@webhooks-cc/mcp 1.10.1 → 1.12.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 +12 -13
- package/dist/bin/mcp.js +489 -167
- package/dist/index.d.mts +21 -6
- package/dist/index.d.ts +21 -6
- package/dist/index.js +494 -167
- package/dist/index.mjs +496 -168
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { WebhooksCC as
|
|
3
|
+
import { WebhooksCC as WebhooksCC3 } from "@webhooks-cc/sdk";
|
|
4
4
|
|
|
5
5
|
// src/prompts.ts
|
|
6
6
|
import { z } from "zod";
|
|
@@ -377,6 +377,13 @@ function registerResources(server, client) {
|
|
|
377
377
|
);
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
+
// src/sandbox-tools.ts
|
|
381
|
+
import { z as z3 } from "zod";
|
|
382
|
+
import {
|
|
383
|
+
AgentAuthError,
|
|
384
|
+
WebhooksCC as WebhooksCC2
|
|
385
|
+
} from "@webhooks-cc/sdk";
|
|
386
|
+
|
|
380
387
|
// src/tools.ts
|
|
381
388
|
import { z as z2 } from "zod";
|
|
382
389
|
import {
|
|
@@ -399,7 +406,6 @@ import {
|
|
|
399
406
|
UnauthorizedError,
|
|
400
407
|
VERIFY_PROVIDERS,
|
|
401
408
|
verifySignature,
|
|
402
|
-
WebhooksCC,
|
|
403
409
|
WebhooksCCError,
|
|
404
410
|
extractCode,
|
|
405
411
|
extractLink
|
|
@@ -460,6 +466,44 @@ var mockResponseSchema = z2.object({
|
|
|
460
466
|
`Response delay in milliseconds (${MOCK_RESPONSE_DELAY_MIN}-${MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
461
467
|
)
|
|
462
468
|
});
|
|
469
|
+
var createEndpointShape = {
|
|
470
|
+
name: z2.string().optional().describe("Display name for the endpoint"),
|
|
471
|
+
ephemeral: z2.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
472
|
+
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
473
|
+
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
|
|
474
|
+
responseRules: responseRulesSchema.optional().describe(
|
|
475
|
+
"Conditional response rules. Each rule has conditions and a response. First matching rule wins."
|
|
476
|
+
),
|
|
477
|
+
notificationUrl: z2.string().url().optional().describe(
|
|
478
|
+
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
479
|
+
),
|
|
480
|
+
signingProvider: z2.string().optional().describe(
|
|
481
|
+
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
482
|
+
),
|
|
483
|
+
signingSecret: z2.string().optional().describe(
|
|
484
|
+
"Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
|
|
485
|
+
),
|
|
486
|
+
signingHeader: z2.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
|
|
487
|
+
};
|
|
488
|
+
var listEndpointsShape = {
|
|
489
|
+
team: z2.string().min(1).optional().describe(
|
|
490
|
+
"Team id or name (case-insensitive). Keeps endpoints shared with you from that team and endpoints you own that are shared with it."
|
|
491
|
+
)
|
|
492
|
+
};
|
|
493
|
+
var getEndpointShape = { slug: z2.string().describe("The endpoint slug") };
|
|
494
|
+
var deleteEndpointShape = { slug: z2.string().describe("The endpoint slug to delete") };
|
|
495
|
+
var listRequestsShape = {
|
|
496
|
+
endpointSlug: z2.string().describe("The endpoint slug"),
|
|
497
|
+
limit: z2.number().int().min(1).max(100).default(25).describe("Max requests to return"),
|
|
498
|
+
since: z2.number().optional().describe("Only return requests after this timestamp in ms"),
|
|
499
|
+
kind: kindSchema
|
|
500
|
+
};
|
|
501
|
+
var getRequestShape = { requestId: z2.string().describe("The request ID") };
|
|
502
|
+
var waitForRequestShape = {
|
|
503
|
+
endpointSlug: z2.string().describe("The endpoint slug to monitor"),
|
|
504
|
+
timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
|
|
505
|
+
pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
|
|
506
|
+
};
|
|
463
507
|
function textContent(text) {
|
|
464
508
|
return { content: [{ type: "text", text }] };
|
|
465
509
|
}
|
|
@@ -763,25 +807,7 @@ function registerTools(server, client) {
|
|
|
763
807
|
server.tool(
|
|
764
808
|
"create_endpoint",
|
|
765
809
|
"Create a webhook endpoint. Returns the endpoint slug, URL, and metadata.",
|
|
766
|
-
|
|
767
|
-
name: z2.string().optional().describe("Display name for the endpoint"),
|
|
768
|
-
ephemeral: z2.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
769
|
-
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
770
|
-
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
|
|
771
|
-
responseRules: responseRulesSchema.optional().describe(
|
|
772
|
-
"Conditional response rules. Each rule has conditions and a response. First matching rule wins."
|
|
773
|
-
),
|
|
774
|
-
notificationUrl: z2.string().url().optional().describe(
|
|
775
|
-
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
776
|
-
),
|
|
777
|
-
signingProvider: z2.string().optional().describe(
|
|
778
|
-
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
779
|
-
),
|
|
780
|
-
signingSecret: z2.string().optional().describe(
|
|
781
|
-
"Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
|
|
782
|
-
),
|
|
783
|
-
signingHeader: z2.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
|
|
784
|
-
},
|
|
810
|
+
createEndpointShape,
|
|
785
811
|
withErrorHandling(
|
|
786
812
|
async ({
|
|
787
813
|
name,
|
|
@@ -834,11 +860,7 @@ function registerTools(server, client) {
|
|
|
834
860
|
server.tool(
|
|
835
861
|
"list_endpoints",
|
|
836
862
|
"List webhook endpoints: those you own (with sharedWith) and those shared with you through teams (with fromTeam). Optionally keep only one team's endpoints.",
|
|
837
|
-
|
|
838
|
-
team: z2.string().min(1).optional().describe(
|
|
839
|
-
"Team id or name (case-insensitive). Keeps endpoints shared with you from that team and endpoints you own that are shared with it."
|
|
840
|
-
)
|
|
841
|
-
},
|
|
863
|
+
listEndpointsShape,
|
|
842
864
|
withErrorHandling(async ({ team }) => {
|
|
843
865
|
const endpoints = await client.endpoints.list({ team });
|
|
844
866
|
return jsonContent(endpoints);
|
|
@@ -894,7 +916,7 @@ function registerTools(server, client) {
|
|
|
894
916
|
server.tool(
|
|
895
917
|
"get_endpoint",
|
|
896
918
|
"Get details for a specific webhook endpoint by slug.",
|
|
897
|
-
|
|
919
|
+
getEndpointShape,
|
|
898
920
|
withErrorHandling(async ({ slug }) => {
|
|
899
921
|
const endpoint = await client.endpoints.get(slug);
|
|
900
922
|
return jsonContent(endpoint);
|
|
@@ -947,7 +969,7 @@ function registerTools(server, client) {
|
|
|
947
969
|
server.tool(
|
|
948
970
|
"delete_endpoint",
|
|
949
971
|
"Delete a webhook endpoint and all its captured requests.",
|
|
950
|
-
|
|
972
|
+
deleteEndpointShape,
|
|
951
973
|
withErrorHandling(async ({ slug }) => {
|
|
952
974
|
await client.endpoints.delete(slug);
|
|
953
975
|
return textContent(`Endpoint "${slug}" deleted.`);
|
|
@@ -1051,12 +1073,7 @@ function registerTools(server, client) {
|
|
|
1051
1073
|
server.tool(
|
|
1052
1074
|
"list_requests",
|
|
1053
1075
|
"List recent captured requests for an endpoint, HTTP requests and emails together (filter with kind). For emails, list_emails is shorter.",
|
|
1054
|
-
|
|
1055
|
-
endpointSlug: z2.string().describe("The endpoint slug"),
|
|
1056
|
-
limit: z2.number().int().min(1).max(100).default(25).describe("Max requests to return"),
|
|
1057
|
-
since: z2.number().optional().describe("Only return requests after this timestamp in ms"),
|
|
1058
|
-
kind: kindSchema
|
|
1059
|
-
},
|
|
1076
|
+
listRequestsShape,
|
|
1060
1077
|
withErrorHandling(async ({ endpointSlug, limit, since, kind }) => {
|
|
1061
1078
|
const requests = await client.requests.list(endpointSlug, { limit, since, kind });
|
|
1062
1079
|
return jsonContent(requests.map(compactRequest));
|
|
@@ -1110,7 +1127,7 @@ function registerTools(server, client) {
|
|
|
1110
1127
|
server.tool(
|
|
1111
1128
|
"get_request",
|
|
1112
1129
|
"Get full details for a specific captured request by ID.",
|
|
1113
|
-
|
|
1130
|
+
getRequestShape,
|
|
1114
1131
|
withErrorHandling(async ({ requestId }) => {
|
|
1115
1132
|
const request = await client.requests.get(requestId);
|
|
1116
1133
|
return jsonContent(compactRequest(request));
|
|
@@ -1119,11 +1136,7 @@ function registerTools(server, client) {
|
|
|
1119
1136
|
server.tool(
|
|
1120
1137
|
"wait_for_request",
|
|
1121
1138
|
"Wait for a request to arrive at an endpoint.",
|
|
1122
|
-
|
|
1123
|
-
endpointSlug: z2.string().describe("The endpoint slug to monitor"),
|
|
1124
|
-
timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
|
|
1125
|
-
pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
|
|
1126
|
-
},
|
|
1139
|
+
waitForRequestShape,
|
|
1127
1140
|
withErrorHandling(async ({ endpointSlug, timeout, pollInterval }) => {
|
|
1128
1141
|
const request = await client.requests.waitFor(endpointSlug, { timeout, pollInterval });
|
|
1129
1142
|
return jsonContent(compactRequest(request));
|
|
@@ -1544,25 +1557,52 @@ function registerTools(server, client) {
|
|
|
1544
1557
|
);
|
|
1545
1558
|
server.tool(
|
|
1546
1559
|
"configure_forwarding",
|
|
1547
|
-
"
|
|
1560
|
+
"Change an endpoint's forwarding: the URL, on or off, which kinds (HTTP requests, emails), the format, your own headers, and the advanced options. Only the arguments you pass change. Owner only. Saving the first URL creates the signing secret. A Slack or Discord webhook URL gets a chat message automatically.",
|
|
1548
1561
|
{
|
|
1549
1562
|
slug: z2.string().describe("The endpoint slug"),
|
|
1550
1563
|
url: httpUrlSchema.nullable().optional().describe("https URL on a public host name, or null to remove it (forwarding must be off)"),
|
|
1551
|
-
enabled: z2.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)")
|
|
1564
|
+
enabled: z2.boolean().optional().describe("Turn forwarding on (needs a URL) or off (fails deliveries still waiting)"),
|
|
1565
|
+
http: z2.boolean().optional().describe("Forward HTTP requests"),
|
|
1566
|
+
email: z2.boolean().optional().describe("Forward emails"),
|
|
1567
|
+
format: z2.enum(["auto", "as_received", "json", "chat"]).optional().describe(
|
|
1568
|
+
"auto (pick from the URL), as_received (relay HTTP requests unchanged; emails as signed JSON), json (signed JSON envelope for both kinds) or chat (Slack or Discord message)"
|
|
1569
|
+
),
|
|
1570
|
+
headers: z2.array(
|
|
1571
|
+
z2.object({
|
|
1572
|
+
name: z2.string().describe("Header name, e.g. Authorization"),
|
|
1573
|
+
value: z2.string().nullable().describe("Header value, or null to keep the value stored under this name")
|
|
1574
|
+
})
|
|
1575
|
+
).max(10).optional().describe(
|
|
1576
|
+
"Your own headers for every delivery (not chat). Replaces the whole list; [] removes them"
|
|
1577
|
+
),
|
|
1578
|
+
appendPath: z2.boolean().optional().describe("Add the path after the slug to the URL for relayed requests (default true)"),
|
|
1579
|
+
retrySeconds: z2.union([z2.literal(0), z2.literal(3600), z2.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
|
|
1580
|
+
keepOrder: z2.boolean().optional().describe("One delivery at a time, in capture order (default false)"),
|
|
1581
|
+
sentField: z2.string().max(128).nullable().optional().describe(
|
|
1582
|
+
"JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
|
|
1583
|
+
)
|
|
1552
1584
|
},
|
|
1553
|
-
withErrorHandling(async ({ slug,
|
|
1554
|
-
const endpoint = await client.forwarding.configure(slug,
|
|
1585
|
+
withErrorHandling(async ({ slug, ...options }) => {
|
|
1586
|
+
const endpoint = await client.forwarding.configure(slug, options);
|
|
1555
1587
|
return jsonContent({
|
|
1556
1588
|
slug: endpoint.slug,
|
|
1557
1589
|
forwardEnabled: endpoint.forwardEnabled ?? false,
|
|
1558
1590
|
forwardUrl: endpoint.forwardUrl ?? null,
|
|
1559
|
-
hasForwardSecret: endpoint.hasForwardSecret ?? false
|
|
1591
|
+
hasForwardSecret: endpoint.hasForwardSecret ?? false,
|
|
1592
|
+
forwardHttp: endpoint.forwardHttp ?? false,
|
|
1593
|
+
forwardEmail: endpoint.forwardEmail ?? false,
|
|
1594
|
+
forwardFormat: endpoint.forwardFormat ?? "auto",
|
|
1595
|
+
forwardHeaders: endpoint.forwardHeaders ?? [],
|
|
1596
|
+
forwardAppendPath: endpoint.forwardAppendPath ?? true,
|
|
1597
|
+
forwardRetrySeconds: endpoint.forwardRetrySeconds ?? 86400,
|
|
1598
|
+
forwardKeepOrder: endpoint.forwardKeepOrder ?? false,
|
|
1599
|
+
forwardSentField: endpoint.forwardSentField ?? null
|
|
1560
1600
|
});
|
|
1561
1601
|
})
|
|
1562
1602
|
);
|
|
1563
1603
|
server.tool(
|
|
1564
1604
|
"get_forwarding_secret",
|
|
1565
|
-
"Read (or with rotate: true, replace) the whsec_ secret forwarded
|
|
1605
|
+
"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.",
|
|
1566
1606
|
{
|
|
1567
1607
|
slug: z2.string().describe("The endpoint slug"),
|
|
1568
1608
|
rotate: z2.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
|
|
@@ -1574,35 +1614,57 @@ function registerTools(server, client) {
|
|
|
1574
1614
|
);
|
|
1575
1615
|
server.tool(
|
|
1576
1616
|
"test_forwarding",
|
|
1577
|
-
"
|
|
1578
|
-
{
|
|
1579
|
-
|
|
1580
|
-
|
|
1617
|
+
"Send the endpoint's newest captured request of a kind (or a sample) to its forwarding URL once, exactly as a delivery would go out, and report the answer. Works with forwarding on or off; nothing is retried or logged.",
|
|
1618
|
+
{
|
|
1619
|
+
slug: z2.string().describe("The endpoint slug"),
|
|
1620
|
+
kind: z2.enum(["http", "email"]).optional().describe(
|
|
1621
|
+
"Which kind to send (default: http when HTTP requests are forwarded, else email)"
|
|
1622
|
+
)
|
|
1623
|
+
},
|
|
1624
|
+
withErrorHandling(async ({ slug, kind }) => {
|
|
1625
|
+
return jsonContent(await client.forwarding.test(slug, kind ? { kind } : {}));
|
|
1581
1626
|
})
|
|
1582
1627
|
);
|
|
1583
1628
|
server.tool(
|
|
1584
1629
|
"list_deliveries",
|
|
1585
|
-
"List forwarding deliveries: an endpoint's
|
|
1630
|
+
"List forwarding deliveries: an endpoint's delivery log with its counts (endpointSlug), or every delivery and try of one captured request or email (requestId). Rows carry senderAt when the request had the sender's own timestamp, so receivedAt - senderAt is the time to reach webhooks.cc.",
|
|
1586
1631
|
{
|
|
1587
|
-
endpointSlug: z2.string().optional().describe("List the endpoint's
|
|
1588
|
-
requestId: z2.string().optional().describe("List every delivery and try of this
|
|
1589
|
-
limit: z2.number().int().min(1).max(
|
|
1632
|
+
endpointSlug: z2.string().optional().describe("List the endpoint's delivery log"),
|
|
1633
|
+
requestId: z2.string().optional().describe("List every delivery and try of this request"),
|
|
1634
|
+
limit: z2.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
|
|
1635
|
+
status: z2.enum(["all", "pending", "failed"]).default("all").describe("With endpointSlug: all, pending (queued or retrying) or failed"),
|
|
1636
|
+
before: z2.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
|
|
1590
1637
|
},
|
|
1591
|
-
withErrorHandling(async ({ endpointSlug, requestId, limit }) => {
|
|
1638
|
+
withErrorHandling(async ({ endpointSlug, requestId, limit, status, before }) => {
|
|
1592
1639
|
if (requestId === void 0 === (endpointSlug === void 0)) {
|
|
1593
1640
|
throw new Error("Pass exactly one of endpointSlug or requestId");
|
|
1594
1641
|
}
|
|
1595
|
-
return jsonContent(
|
|
1596
|
-
|
|
1597
|
-
|
|
1642
|
+
if (requestId) return jsonContent(await client.forwarding.requestDeliveries(requestId));
|
|
1643
|
+
const [summary, deliveries] = await Promise.all([
|
|
1644
|
+
client.forwarding.summary(endpointSlug),
|
|
1645
|
+
client.forwarding.deliveries(endpointSlug, { limit, status, before })
|
|
1646
|
+
]);
|
|
1647
|
+
return jsonContent({
|
|
1648
|
+
summary,
|
|
1649
|
+
deliveries,
|
|
1650
|
+
nextCursor: deliveries.length === limit ? deliveries[deliveries.length - 1]?.cursor ?? null : null
|
|
1651
|
+
});
|
|
1598
1652
|
})
|
|
1599
1653
|
);
|
|
1600
1654
|
server.tool(
|
|
1601
|
-
"
|
|
1602
|
-
"Forward
|
|
1603
|
-
{
|
|
1604
|
-
|
|
1605
|
-
|
|
1655
|
+
"redeliver",
|
|
1656
|
+
"Forward again with the endpoint's current settings: one captured request or email (requestId; it also sends one that was never forwarded), or every request whose latest delivery failed (endpointSlug, oldest first, up to 1,000 at a time). Owner only; fails while forwarding is off.",
|
|
1657
|
+
{
|
|
1658
|
+
requestId: z2.string().optional().describe("The request or email to send again"),
|
|
1659
|
+
endpointSlug: z2.string().optional().describe("Send every failed delivery of this endpoint again")
|
|
1660
|
+
},
|
|
1661
|
+
withErrorHandling(async ({ requestId, endpointSlug }) => {
|
|
1662
|
+
if (requestId === void 0 === (endpointSlug === void 0)) {
|
|
1663
|
+
throw new Error("Pass exactly one of requestId or endpointSlug");
|
|
1664
|
+
}
|
|
1665
|
+
return jsonContent(
|
|
1666
|
+
requestId ? await client.forwarding.redeliver(requestId) : await client.forwarding.redeliverFailed(endpointSlug)
|
|
1667
|
+
);
|
|
1606
1668
|
})
|
|
1607
1669
|
);
|
|
1608
1670
|
server.tool(
|
|
@@ -1614,127 +1676,392 @@ function registerTools(server, client) {
|
|
|
1614
1676
|
return jsonContent(description);
|
|
1615
1677
|
})
|
|
1616
1678
|
);
|
|
1617
|
-
registerAgentRegistrationTools(server);
|
|
1618
1679
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1680
|
+
|
|
1681
|
+
// src/sandbox-tools.ts
|
|
1682
|
+
var SANDBOX = {
|
|
1683
|
+
maxEndpoints: 3,
|
|
1684
|
+
requestsPerEndpoint: 25,
|
|
1685
|
+
requests: 100,
|
|
1686
|
+
lifetimeHours: 24
|
|
1687
|
+
};
|
|
1688
|
+
var DEFAULT_WAIT_SECONDS = 45;
|
|
1689
|
+
var MAX_WAIT_SECONDS = 600;
|
|
1690
|
+
var TOKEN_REFRESH_MARGIN_MS = 6e4;
|
|
1691
|
+
var DOCS_URL = "https://webhooks.cc/docs/agents";
|
|
1692
|
+
var ACCOUNT_ONLY_OPTIONS = [
|
|
1693
|
+
"mockResponse",
|
|
1694
|
+
"responseRules",
|
|
1695
|
+
"notificationUrl",
|
|
1696
|
+
"signingProvider",
|
|
1697
|
+
"signingSecret",
|
|
1698
|
+
"signingHeader"
|
|
1699
|
+
];
|
|
1700
|
+
var AgentSession = class {
|
|
1701
|
+
constructor(baseUrl, webhookUrl, clientName) {
|
|
1702
|
+
this.baseUrl = baseUrl;
|
|
1703
|
+
this.webhookUrl = webhookUrl;
|
|
1704
|
+
this.clientName = clientName;
|
|
1705
|
+
this.sandbox = null;
|
|
1706
|
+
this.claim = null;
|
|
1707
|
+
this.connected = false;
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* The sandbox, registered on first use and again after it expired. Tool
|
|
1711
|
+
* calls can run in parallel: this.sandbox is read again after every await,
|
|
1712
|
+
* and a new sandbox is assigned with no await in between, so parallel
|
|
1713
|
+
* calls share one registration. Calls waiting on a registration that fails
|
|
1714
|
+
* get its error (a 429 asks them to wait); the next call starts again.
|
|
1715
|
+
*/
|
|
1716
|
+
async sandboxClient() {
|
|
1717
|
+
while (this.sandbox) {
|
|
1718
|
+
const pending = this.sandbox;
|
|
1719
|
+
const sandbox = await pending;
|
|
1720
|
+
if (!this.expired(pending, sandbox)) return sandbox;
|
|
1721
|
+
}
|
|
1722
|
+
const created = WebhooksCC2.sandbox({
|
|
1723
|
+
baseUrl: this.baseUrl,
|
|
1724
|
+
webhookUrl: this.webhookUrl,
|
|
1725
|
+
clientName: this.clientName
|
|
1726
|
+
});
|
|
1727
|
+
this.sandbox = created;
|
|
1728
|
+
created.catch(() => {
|
|
1729
|
+
if (this.sandbox === created) this.sandbox = null;
|
|
1730
|
+
});
|
|
1731
|
+
return created;
|
|
1732
|
+
}
|
|
1733
|
+
/** The live sandbox, if one was registered; never registers one. */
|
|
1734
|
+
async existingSandbox() {
|
|
1735
|
+
const pending = this.sandbox;
|
|
1736
|
+
if (!pending) return null;
|
|
1737
|
+
let sandbox;
|
|
1738
|
+
try {
|
|
1739
|
+
sandbox = await pending;
|
|
1740
|
+
} catch {
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
return this.expired(pending, sandbox) ? null : sandbox;
|
|
1744
|
+
}
|
|
1745
|
+
/** True when the sandbox is past its 24 hours; then it is dropped. */
|
|
1746
|
+
expired(pending, sandbox) {
|
|
1747
|
+
if (sandbox.expiresAt.getTime() > Date.now()) return false;
|
|
1748
|
+
if (this.sandbox === pending) this.sandbox = null;
|
|
1749
|
+
if (this.claim?.kind === "sandbox" && this.claim.claimToken === sandbox.registration.claimToken) {
|
|
1750
|
+
this.claim = null;
|
|
1751
|
+
}
|
|
1752
|
+
return true;
|
|
1753
|
+
}
|
|
1754
|
+
async requireSandbox() {
|
|
1755
|
+
const sandbox = await this.existingSandbox();
|
|
1756
|
+
if (!sandbox) {
|
|
1757
|
+
throw new Error("No sandbox yet: create_endpoint starts one.");
|
|
1758
|
+
}
|
|
1759
|
+
return sandbox;
|
|
1760
|
+
}
|
|
1761
|
+
forgetSandbox() {
|
|
1762
|
+
this.sandbox = null;
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
function claimedTokens(baseUrl, assertion, first) {
|
|
1766
|
+
let current = first;
|
|
1767
|
+
let pending = null;
|
|
1768
|
+
return async (options) => {
|
|
1769
|
+
if (!options?.forceRefresh && current.expiresAt.getTime() - Date.now() > TOKEN_REFRESH_MARGIN_MS) {
|
|
1770
|
+
return current.accessToken;
|
|
1771
|
+
}
|
|
1772
|
+
if (!pending) {
|
|
1773
|
+
pending = WebhooksCC2.agent.exchange({ baseUrl, assertion }).finally(() => {
|
|
1774
|
+
pending = null;
|
|
1635
1775
|
});
|
|
1636
|
-
}
|
|
1776
|
+
}
|
|
1777
|
+
current = await pending;
|
|
1778
|
+
return current.accessToken;
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
function minutesLeft(until) {
|
|
1782
|
+
return Math.max(1, Math.round((until.getTime() - Date.now()) / 6e4));
|
|
1783
|
+
}
|
|
1784
|
+
function onlyKind(requests, kind) {
|
|
1785
|
+
if (!kind) return requests;
|
|
1786
|
+
return requests.filter((request) => (request.kind ?? "http") === kind);
|
|
1787
|
+
}
|
|
1788
|
+
function registerSandboxTools(server, options = {}) {
|
|
1789
|
+
const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL ?? "https://webhooks.cc";
|
|
1790
|
+
const session = new AgentSession(
|
|
1791
|
+
baseUrl,
|
|
1792
|
+
options.webhookUrl ?? process.env.WHK_WEBHOOK_URL,
|
|
1793
|
+
options.clientName ?? "webhooks-cc-mcp"
|
|
1637
1794
|
);
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1795
|
+
const registered = [];
|
|
1796
|
+
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`;
|
|
1797
|
+
registered.push(
|
|
1798
|
+
server.tool(
|
|
1799
|
+
"create_endpoint",
|
|
1800
|
+
`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.`,
|
|
1801
|
+
createEndpointShape,
|
|
1802
|
+
withErrorHandling(async (args) => {
|
|
1803
|
+
const accountOnly = ACCOUNT_ONLY_OPTIONS.filter((key) => args[key] !== void 0);
|
|
1804
|
+
if (accountOnly.length > 0) {
|
|
1805
|
+
throw new Error(
|
|
1806
|
+
`${accountOnly.join(", ")} need an account; sandbox endpoints only capture and answer 200. Call connect_account to connect one.`
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
const sandbox = await session.sandboxClient();
|
|
1810
|
+
const endpoint = await sandbox.endpoints.create();
|
|
1811
|
+
const ignored = args.name !== void 0 || args.expiresIn !== void 0;
|
|
1812
|
+
return jsonContent(
|
|
1813
|
+
ignored ? {
|
|
1814
|
+
...endpoint,
|
|
1815
|
+
note: "Sandbox endpoints have no name and expire with the sandbox."
|
|
1816
|
+
} : endpoint
|
|
1817
|
+
);
|
|
1818
|
+
})
|
|
1819
|
+
)
|
|
1659
1820
|
);
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1821
|
+
registered.push(
|
|
1822
|
+
server.tool(
|
|
1823
|
+
"list_endpoints",
|
|
1824
|
+
"List this agent's sandbox endpoints. Empty until create_endpoint starts the sandbox.",
|
|
1825
|
+
listEndpointsShape,
|
|
1826
|
+
withErrorHandling(async ({ team }) => {
|
|
1827
|
+
if (team !== void 0) {
|
|
1828
|
+
throw new Error("Teams need an account. Call connect_account to connect one.");
|
|
1829
|
+
}
|
|
1830
|
+
const sandbox = await session.existingSandbox();
|
|
1831
|
+
return jsonContent(sandbox ? await sandbox.endpoints.list() : []);
|
|
1832
|
+
})
|
|
1833
|
+
)
|
|
1670
1834
|
);
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
return jsonContent({
|
|
1681
|
-
claimToken: challenge.claimToken,
|
|
1682
|
-
expiresAt: challenge.claimTokenExpires,
|
|
1683
|
-
postClaimScopes: challenge.postClaimScopes,
|
|
1684
|
-
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.`
|
|
1685
|
-
});
|
|
1686
|
-
})
|
|
1835
|
+
registered.push(
|
|
1836
|
+
server.tool(
|
|
1837
|
+
"get_endpoint",
|
|
1838
|
+
"Get one of this agent's sandbox endpoints by slug, with its sandbox limits and usage.",
|
|
1839
|
+
getEndpointShape,
|
|
1840
|
+
withErrorHandling(async ({ slug }) => {
|
|
1841
|
+
return jsonContent(await (await session.requireSandbox()).endpoints.get(slug));
|
|
1842
|
+
})
|
|
1843
|
+
)
|
|
1687
1844
|
);
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
credential: issued.credential,
|
|
1699
|
-
scopes: issued.scopes,
|
|
1700
|
-
usage: "Set WHK_API_KEY to `credential` to use the authenticated tools."
|
|
1701
|
-
});
|
|
1702
|
-
})
|
|
1845
|
+
registered.push(
|
|
1846
|
+
server.tool(
|
|
1847
|
+
"delete_endpoint",
|
|
1848
|
+
"Delete one of this agent's sandbox endpoints and its captured requests. Frees a slot; the sandbox's request budget is not refunded.",
|
|
1849
|
+
deleteEndpointShape,
|
|
1850
|
+
withErrorHandling(async ({ slug }) => {
|
|
1851
|
+
await (await session.requireSandbox()).endpoints.delete(slug);
|
|
1852
|
+
return jsonContent({ deleted: slug });
|
|
1853
|
+
})
|
|
1854
|
+
)
|
|
1703
1855
|
);
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1856
|
+
registered.push(
|
|
1857
|
+
server.tool(
|
|
1858
|
+
"list_requests",
|
|
1859
|
+
"List recent requests captured by a sandbox endpoint, newest first.",
|
|
1860
|
+
listRequestsShape,
|
|
1861
|
+
withErrorHandling(async ({ endpointSlug, limit, since, kind }) => {
|
|
1862
|
+
const sandbox = await session.requireSandbox();
|
|
1863
|
+
const requests = await sandbox.requests.list(endpointSlug, { limit, since });
|
|
1864
|
+
return jsonContent(onlyKind(requests, kind).map(compactRequest));
|
|
1865
|
+
})
|
|
1866
|
+
)
|
|
1867
|
+
);
|
|
1868
|
+
registered.push(
|
|
1869
|
+
server.tool(
|
|
1870
|
+
"get_request",
|
|
1871
|
+
"Get full details for a request captured by a sandbox endpoint.",
|
|
1872
|
+
getRequestShape,
|
|
1873
|
+
withErrorHandling(async ({ requestId }) => {
|
|
1874
|
+
const sandbox = await session.requireSandbox();
|
|
1875
|
+
return jsonContent(compactRequest(await sandbox.requests.get(requestId)));
|
|
1876
|
+
})
|
|
1877
|
+
)
|
|
1878
|
+
);
|
|
1879
|
+
registered.push(
|
|
1880
|
+
server.tool(
|
|
1881
|
+
"wait_for_request",
|
|
1882
|
+
"Wait for a request to arrive at a sandbox endpoint.",
|
|
1883
|
+
waitForRequestShape,
|
|
1884
|
+
withErrorHandling(async ({ endpointSlug, timeout, pollInterval }) => {
|
|
1885
|
+
const sandbox = await session.requireSandbox();
|
|
1886
|
+
const request = await sandbox.requests.waitFor(endpointSlug, { timeout, pollInterval });
|
|
1887
|
+
return jsonContent(compactRequest(request));
|
|
1888
|
+
})
|
|
1889
|
+
)
|
|
1890
|
+
);
|
|
1891
|
+
registered.push(
|
|
1892
|
+
server.tool(
|
|
1893
|
+
"connect_account",
|
|
1894
|
+
"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.",
|
|
1895
|
+
{
|
|
1896
|
+
email: z3.string().email({ message: "Invalid email address" }).describe("The human's email address; only someone signed in as it can connect")
|
|
1897
|
+
},
|
|
1898
|
+
withErrorHandling(async ({ email }) => {
|
|
1899
|
+
if (session.connected) {
|
|
1900
|
+
return jsonContent({ connected: true, message: "Already connected to an account." });
|
|
1901
|
+
}
|
|
1902
|
+
const normalized = email.trim().toLowerCase();
|
|
1903
|
+
const sandbox = await session.existingSandbox();
|
|
1904
|
+
let claim;
|
|
1905
|
+
if (sandbox) {
|
|
1906
|
+
claim = {
|
|
1907
|
+
kind: "sandbox",
|
|
1908
|
+
claimToken: sandbox.registration.claimToken,
|
|
1909
|
+
email: normalized,
|
|
1910
|
+
attempt: await sandbox.claim({ email: normalized }),
|
|
1911
|
+
claimExpires: sandbox.expiresAt
|
|
1912
|
+
};
|
|
1913
|
+
} else if (session.claim?.kind === "service_auth" && session.claim.email === normalized && session.claim.claimExpires.getTime() > Date.now()) {
|
|
1914
|
+
const attempt = await WebhooksCC2.agent.startClaim({
|
|
1915
|
+
baseUrl,
|
|
1916
|
+
claimToken: session.claim.claimToken
|
|
1917
|
+
});
|
|
1918
|
+
claim = { ...session.claim, attempt };
|
|
1919
|
+
} else {
|
|
1920
|
+
const registration = await WebhooksCC2.agent.registerServiceAuth({
|
|
1921
|
+
baseUrl,
|
|
1922
|
+
email: normalized,
|
|
1923
|
+
clientName: session.clientName
|
|
1924
|
+
});
|
|
1925
|
+
claim = {
|
|
1926
|
+
kind: "service_auth",
|
|
1927
|
+
claimToken: registration.claimToken,
|
|
1928
|
+
email: normalized,
|
|
1929
|
+
attempt: registration.claim,
|
|
1930
|
+
claimExpires: registration.claimTokenExpires
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
session.claim = claim;
|
|
1934
|
+
const { verificationUri, userCode, expiresAt } = claim.attempt;
|
|
1935
|
+
return jsonContent({
|
|
1936
|
+
verificationUri,
|
|
1937
|
+
userCode,
|
|
1938
|
+
expiresAt: expiresAt.toISOString(),
|
|
1939
|
+
tellTheHuman: `Open ${verificationUri}, sign in or sign up as ${normalized}, and enter the code ${userCode}.`,
|
|
1940
|
+
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.`,
|
|
1941
|
+
...claim.kind === "sandbox" ? { endpoints: "The sandbox endpoints move into that account with what they captured." } : {}
|
|
1942
|
+
});
|
|
1943
|
+
})
|
|
1944
|
+
)
|
|
1945
|
+
);
|
|
1946
|
+
registered.push(
|
|
1947
|
+
server.tool(
|
|
1948
|
+
"wait_for_connection",
|
|
1949
|
+
`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.`,
|
|
1950
|
+
{
|
|
1951
|
+
timeoutSeconds: z3.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
|
|
1952
|
+
},
|
|
1953
|
+
withErrorHandling(async ({ timeoutSeconds }) => {
|
|
1954
|
+
if (session.connected) {
|
|
1955
|
+
return jsonContent({ connected: true, message: "Already connected to an account." });
|
|
1956
|
+
}
|
|
1957
|
+
const claim = session.claim;
|
|
1958
|
+
if (!claim) {
|
|
1959
|
+
throw new Error("Nothing to wait for: call connect_account first.");
|
|
1960
|
+
}
|
|
1961
|
+
let claimed;
|
|
1962
|
+
try {
|
|
1963
|
+
claimed = await WebhooksCC2.agent.waitForClaim({
|
|
1964
|
+
baseUrl,
|
|
1965
|
+
claimToken: claim.claimToken,
|
|
1966
|
+
interval: claim.attempt.interval,
|
|
1967
|
+
timeout: (timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3
|
|
1968
|
+
});
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
if (!(error instanceof AgentAuthError)) throw error;
|
|
1971
|
+
if (error.code === "timeout") {
|
|
1972
|
+
return jsonContent({
|
|
1973
|
+
connected: false,
|
|
1974
|
+
status: "waiting",
|
|
1975
|
+
message: `Not connected yet. The code works until ${claim.attempt.expiresAt.toISOString()}; call wait_for_connection again.`
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
if (error.code === "expired_token") {
|
|
1979
|
+
return jsonContent({
|
|
1980
|
+
connected: false,
|
|
1981
|
+
status: "expired",
|
|
1982
|
+
message: "The code expired. Call connect_account for a new one."
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
if (error.code === "access_denied") {
|
|
1986
|
+
return jsonContent({
|
|
1987
|
+
connected: false,
|
|
1988
|
+
status: "declined",
|
|
1989
|
+
message: "The human declined. Call connect_account again only if they ask you to."
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
throw error;
|
|
1993
|
+
}
|
|
1994
|
+
const client = new WebhooksCC2({
|
|
1995
|
+
baseUrl,
|
|
1996
|
+
webhookUrl: session.webhookUrl,
|
|
1997
|
+
getAccessToken: claimedTokens(baseUrl, claimed.identityAssertion, claimed.token)
|
|
1998
|
+
});
|
|
1999
|
+
session.connected = true;
|
|
2000
|
+
session.claim = null;
|
|
2001
|
+
session.forgetSandbox();
|
|
2002
|
+
for (const tool of registered) tool.remove();
|
|
2003
|
+
registerTools(server, client);
|
|
2004
|
+
options.onConnected?.(client);
|
|
2005
|
+
return jsonContent({
|
|
2006
|
+
connected: true,
|
|
2007
|
+
email: claim.email,
|
|
2008
|
+
scope: claimed.token.scope,
|
|
2009
|
+
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.`
|
|
2010
|
+
});
|
|
2011
|
+
})
|
|
2012
|
+
)
|
|
2013
|
+
);
|
|
2014
|
+
registered.push(
|
|
2015
|
+
server.tool(
|
|
2016
|
+
"about_sandbox",
|
|
2017
|
+
"Explain what this agent can do without a webhooks.cc account, the sandbox limits, and how to connect an account. No network call.",
|
|
2018
|
+
{},
|
|
2019
|
+
withErrorHandling(async () => {
|
|
2020
|
+
return jsonContent({
|
|
2021
|
+
mode: "sandbox (no WHK_API_KEY)",
|
|
2022
|
+
sandbox: {
|
|
2023
|
+
maxEndpoints: SANDBOX.maxEndpoints,
|
|
2024
|
+
requestsPerEndpoint: SANDBOX.requestsPerEndpoint,
|
|
2025
|
+
requestsInAll: SANDBOX.requests,
|
|
2026
|
+
lifetimeHours: SANDBOX.lifetimeHours,
|
|
2027
|
+
startsWith: "create_endpoint (registers the sandbox; a few seconds of proof of work)",
|
|
2028
|
+
captures: "Endpoints answer every request with 200; only this agent can read what they capture.",
|
|
2029
|
+
notAvailable: "Mock responses, response rules, notifications, signature verification, email capture, forwarding, teams and sending webhooks."
|
|
2030
|
+
},
|
|
2031
|
+
connectAnAccount: [
|
|
2032
|
+
"Call connect_account with the human's email and show them the link and code it returns.",
|
|
2033
|
+
"Call wait_for_connection until it reports connected; the full tools for their account then replace the sandbox tools.",
|
|
2034
|
+
"Sandbox endpoints move into the account with their captured requests."
|
|
2035
|
+
],
|
|
2036
|
+
withAnApiKey: "Set WHK_API_KEY to a whcc_ key from the dashboard and restart for the full tools from the start.",
|
|
2037
|
+
docs: DOCS_URL,
|
|
2038
|
+
protocol: `${baseUrl}/auth.md`
|
|
2039
|
+
});
|
|
2040
|
+
})
|
|
2041
|
+
)
|
|
1718
2042
|
);
|
|
1719
2043
|
}
|
|
2044
|
+
function registerAgentRegistrationTools(server) {
|
|
2045
|
+
registerSandboxTools(server);
|
|
2046
|
+
}
|
|
1720
2047
|
|
|
1721
2048
|
// src/index.ts
|
|
1722
|
-
var VERSION = true ? "1.
|
|
2049
|
+
var VERSION = true ? "1.12.0" : "0.0.0-dev";
|
|
1723
2050
|
function createServer(options = {}) {
|
|
1724
2051
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
2052
|
+
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
2053
|
+
const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL;
|
|
2054
|
+
const server = new McpServer2(
|
|
2055
|
+
{ name: "webhooks-cc", version: VERSION },
|
|
2056
|
+
// Connecting an account swaps every tool at once: one list_changed, not dozens.
|
|
2057
|
+
{ debouncedNotificationMethods: ["notifications/tools/list_changed"] }
|
|
2058
|
+
);
|
|
1729
2059
|
if (!apiKey) {
|
|
1730
|
-
|
|
2060
|
+
registerSandboxTools(server, { baseUrl, webhookUrl });
|
|
2061
|
+
registerPrompts(server);
|
|
1731
2062
|
return server;
|
|
1732
2063
|
}
|
|
1733
|
-
const client = new
|
|
1734
|
-
apiKey,
|
|
1735
|
-
webhookUrl: options.webhookUrl ?? process.env.WHK_WEBHOOK_URL,
|
|
1736
|
-
baseUrl: options.baseUrl ?? process.env.WHK_BASE_URL
|
|
1737
|
-
});
|
|
2064
|
+
const client = new WebhooksCC3({ apiKey, webhookUrl, baseUrl });
|
|
1738
2065
|
registerTools(server, client);
|
|
1739
2066
|
registerPrompts(server);
|
|
1740
2067
|
registerResources(server, client);
|
|
@@ -1745,5 +2072,6 @@ export {
|
|
|
1745
2072
|
registerAgentRegistrationTools,
|
|
1746
2073
|
registerPrompts,
|
|
1747
2074
|
registerResources,
|
|
2075
|
+
registerSandboxTools,
|
|
1748
2076
|
registerTools
|
|
1749
2077
|
};
|