@webhooks-cc/mcp 1.5.0 → 1.7.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 CHANGED
@@ -149,10 +149,14 @@ Provider template and signing support includes:
149
149
  - `mux`
150
150
  - `sentry`
151
151
  - `bitbucket`
152
+ - `docusign`
153
+ - `adyen`
154
+ - `paypal`
155
+ - `plaid`
152
156
 
153
157
  Signature verification also supports:
154
158
 
155
- - all template providers except `sendgrid`, which uses IP allowlisting instead of cryptographic signatures
159
+ - all template providers except `sendgrid` (IP allowlisting) and `plaid` (JWT/JWK verification requires Plaid API credentials)
156
160
 
157
161
  Use `list_provider_templates` to inspect templates, default events, signature headers, and signing requirements from the agent.
158
162
 
package/dist/bin/mcp.js CHANGED
@@ -687,15 +687,17 @@ function registerTools(server, client) {
687
687
  provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
688
688
  template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
689
689
  event: import_zod2.z.string().optional().describe("Provider event or topic name"),
690
- secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
690
+ secret: import_zod2.z.string().optional().describe(
691
+ "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
692
+ )
691
693
  },
692
694
  withErrorHandling(
693
695
  async ({ slug, method, headers, body, provider, template, event, secret }) => {
694
696
  let response;
695
697
  if (provider) {
696
698
  const templateSecret = secret?.trim();
697
- if (!templateSecret) {
698
- throw new Error("send_webhook with provider templates requires a non-empty secret");
699
+ if (!templateSecret && import_sdk.TEMPLATE_METADATA[provider]?.secretRequired !== false) {
700
+ throw new Error(`send_webhook with provider "${provider}" requires a non-empty secret`);
699
701
  }
700
702
  response = await client.endpoints.sendTemplate(slug, {
701
703
  provider,
@@ -871,7 +873,7 @@ function registerTools(server, client) {
871
873
  requestId: import_zod2.z.string().describe("The captured request ID"),
872
874
  provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
873
875
  secret: import_zod2.z.string().optional().describe(
874
- "Shared signing secret. Required for non-Discord providers when doing client-side verification."
876
+ "Provider credential for client-side verification (for example a shared signing secret, Adyen hex HMAC key, or PayPal webhook ID). Required for non-Discord providers."
875
877
  ),
876
878
  publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
877
879
  url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio, Square, and HubSpot verification."),
@@ -943,7 +945,9 @@ function registerTools(server, client) {
943
945
  provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
944
946
  template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
945
947
  event: import_zod2.z.string().optional().describe("Provider event or topic name"),
946
- secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
948
+ secret: import_zod2.z.string().optional().describe(
949
+ "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
950
+ )
947
951
  },
948
952
  withErrorHandling(async ({ url, method, headers, body, provider, template, event, secret }) => {
949
953
  const response = await client.sendTo(url, {
@@ -974,7 +978,9 @@ function registerTools(server, client) {
974
978
  provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
975
979
  template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
976
980
  event: import_zod2.z.string().optional().describe("Provider event or topic name"),
977
- secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
981
+ secret: import_zod2.z.string().optional().describe(
982
+ "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
983
+ )
978
984
  },
979
985
  withErrorHandling(async ({ url, method, headers, body, provider, template, event, secret }) => {
980
986
  const preview = await client.buildRequest(url, {
@@ -1050,9 +1056,9 @@ function registerTools(server, client) {
1050
1056
  }
1051
1057
  if (provider) {
1052
1058
  const templateSecret = secret?.trim();
1053
- if (!templateSecret) {
1059
+ if (!templateSecret && import_sdk.TEMPLATE_METADATA[provider]?.secretRequired !== false) {
1054
1060
  throw new Error(
1055
- "test_webhook_flow with provider templates requires a non-empty secret"
1061
+ `test_webhook_flow with provider "${provider}" requires a non-empty secret`
1056
1062
  );
1057
1063
  }
1058
1064
  flow.sendTemplate({
@@ -1066,6 +1072,11 @@ function registerTools(server, client) {
1066
1072
  "test_webhook_flow cannot verify Discord signatures (Ed25519 requires a public key, not a secret)"
1067
1073
  );
1068
1074
  }
1075
+ if (!templateSecret) {
1076
+ throw new Error(
1077
+ `test_webhook_flow cannot verify "${provider}" signatures \u2014 this provider's templates are unsigned`
1078
+ );
1079
+ }
1069
1080
  flow.verifySignature({
1070
1081
  provider,
1071
1082
  secret: templateSecret
@@ -1103,24 +1114,127 @@ function registerTools(server, client) {
1103
1114
  return jsonContent(description);
1104
1115
  })
1105
1116
  );
1117
+ registerAgentRegistrationTools(server);
1118
+ }
1119
+ function resolveBaseUrl() {
1120
+ return process.env.WHK_BASE_URL ?? "https://webhooks.cc";
1121
+ }
1122
+ function registerAgentRegistrationTools(server) {
1123
+ const baseUrl = resolveBaseUrl();
1124
+ server.tool(
1125
+ "how_to_register",
1126
+ "Explain how an agent self-registers for a webhooks.cc API credential (auth.md). Call this FIRST when you have no API key. Returns the three registration flows and the auth.md documentation URL. No authentication required.",
1127
+ {},
1128
+ withErrorHandling(async () => {
1129
+ return jsonContent({
1130
+ ...import_sdk.WebhooksCC.describeRegistration(baseUrl),
1131
+ next_steps: [
1132
+ "Easiest: call register_agent (anonymous) to get a key immediately, then have a human run claim_agent with the returned userCode while logged in to webhooks.cc.",
1133
+ "Or set WHK_API_KEY to an existing whcc_ key and use the authenticated tools."
1134
+ ]
1135
+ });
1136
+ })
1137
+ );
1138
+ server.tool(
1139
+ "register_agent",
1140
+ "Self-register for a webhooks.cc API credential via the anonymous auth.md flow. Returns a whcc_ API key that works immediately in a bounded sandbox, plus a short userCode and claimUrl a human uses to bind the key to their account. No authentication required. After registering, set WHK_API_KEY to the returned credential.",
1141
+ {
1142
+ clientName: import_zod2.z.string().optional().describe("A human-readable name for this agent, recorded on the issued key")
1143
+ },
1144
+ withErrorHandling(async ({ clientName }) => {
1145
+ const reg = await import_sdk.WebhooksCC.register.anonymous({ baseUrl, clientName });
1146
+ return jsonContent({
1147
+ credential: reg.credential,
1148
+ scopes: reg.scopes,
1149
+ claim: {
1150
+ userCode: reg.userCode,
1151
+ claimUrl: reg.claimUrl,
1152
+ claimToken: reg.claimToken,
1153
+ expiresAt: reg.claimTokenExpires,
1154
+ instructions: `Ask a human to open ${reg.claimUrl} while logged in to webhooks.cc and either enter the code ${reg.userCode} or open ${reg.claimUrl}?token=${reg.claimToken}. Then call check_claim with the claimToken.`
1155
+ },
1156
+ usage: "Set WHK_API_KEY to `credential` to use the authenticated tools. Before it is claimed the key can create ephemeral endpoints and read its own captured requests in the sandbox."
1157
+ });
1158
+ })
1159
+ );
1160
+ server.tool(
1161
+ "check_claim",
1162
+ "Check whether an anonymous registration's API key has been claimed by a human yet. Poll this after register_agent until status is 'claimed'. No authentication required.",
1163
+ {
1164
+ claimToken: import_zod2.z.string().describe("The claimToken returned by register_agent")
1165
+ },
1166
+ withErrorHandling(async ({ claimToken }) => {
1167
+ const poll = await import_sdk.WebhooksCC.register.pollClaim(claimToken, { baseUrl });
1168
+ return jsonContent(poll);
1169
+ })
1170
+ );
1171
+ server.tool(
1172
+ "register_agent_with_email",
1173
+ "Self-register via the verified_email auth.md flow: webhooks.cc emails a one-time code to the address. The credential is WITHHELD until the code is confirmed with verify_agent_otp. Returns a claimToken to pass to verify_agent_otp. No authentication required.",
1174
+ {
1175
+ email: import_zod2.z.string().email({ message: "Invalid email address" }).describe("The email address to verify and bind the credential to"),
1176
+ clientName: import_zod2.z.string().optional().describe("A human-readable name for this agent, recorded on the issued key")
1177
+ },
1178
+ withErrorHandling(async ({ email, clientName }) => {
1179
+ const challenge = await import_sdk.WebhooksCC.register.withEmail(email, { baseUrl, clientName });
1180
+ return jsonContent({
1181
+ claimToken: challenge.claimToken,
1182
+ expiresAt: challenge.claimTokenExpires,
1183
+ postClaimScopes: challenge.postClaimScopes,
1184
+ 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.`
1185
+ });
1186
+ })
1187
+ );
1188
+ server.tool(
1189
+ "verify_agent_otp",
1190
+ "Complete the verified_email flow: submit the emailed OTP with the claimToken from register_agent_with_email. On success returns the whcc_ API key (bound to the verified email). Set WHK_API_KEY to it. No authentication required.",
1191
+ {
1192
+ claimToken: import_zod2.z.string().describe("The claimToken returned by register_agent_with_email"),
1193
+ otp: import_zod2.z.string().describe("The one-time code the human received by email")
1194
+ },
1195
+ withErrorHandling(async ({ claimToken, otp }) => {
1196
+ const issued = await import_sdk.WebhooksCC.register.confirmEmailOtp({ claimToken, otp }, { baseUrl });
1197
+ return jsonContent({
1198
+ credential: issued.credential,
1199
+ scopes: issued.scopes,
1200
+ usage: "Set WHK_API_KEY to `credential` to use the authenticated tools."
1201
+ });
1202
+ })
1203
+ );
1204
+ server.tool(
1205
+ "register_agent_with_idjag",
1206
+ "Self-register via the identity_assertion (ID-JAG) auth.md flow: present a verified identity-assertion JWT (urn:ietf:params:oauth:token-type:id-jag) from a trusted provider. The credential is returned synchronously \u2014 no human claim step. No webhooks.cc authentication required, but only works if webhooks.cc trusts your provider.",
1207
+ {
1208
+ assertion: import_zod2.z.string().describe("The ID-JAG assertion JWT (typ oauth-id-jag+jwt) from a trusted provider")
1209
+ },
1210
+ withErrorHandling(async ({ assertion }) => {
1211
+ const issued = await import_sdk.WebhooksCC.register.withIdJag(assertion, { baseUrl });
1212
+ return jsonContent({
1213
+ credential: issued.credential,
1214
+ scopes: issued.scopes,
1215
+ usage: "Set WHK_API_KEY to `credential` to use the authenticated tools."
1216
+ });
1217
+ })
1218
+ );
1106
1219
  }
1107
1220
 
1108
1221
  // src/index.ts
1109
- var VERSION = true ? "1.5.0" : "0.0.0-dev";
1222
+ var VERSION = true ? "1.7.0" : "0.0.0-dev";
1110
1223
  function createServer(options = {}) {
1111
1224
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
1225
+ const server = new import_mcp2.McpServer({
1226
+ name: "webhooks-cc",
1227
+ version: VERSION
1228
+ });
1112
1229
  if (!apiKey) {
1113
- throw new Error("Missing API key. Set WHK_API_KEY environment variable or pass apiKey option.");
1230
+ registerAgentRegistrationTools(server);
1231
+ return server;
1114
1232
  }
1115
1233
  const client = new import_sdk2.WebhooksCC({
1116
1234
  apiKey,
1117
1235
  webhookUrl: options.webhookUrl ?? process.env.WHK_WEBHOOK_URL,
1118
1236
  baseUrl: options.baseUrl ?? process.env.WHK_BASE_URL
1119
1237
  });
1120
- const server = new import_mcp2.McpServer({
1121
- name: "webhooks-cc",
1122
- version: VERSION
1123
- });
1124
1238
  registerTools(server, client);
1125
1239
  registerPrompts(server);
1126
1240
  registerResources(server, client);
package/dist/index.d.mts CHANGED
@@ -3,6 +3,14 @@ import { WebhooksCC } from '@webhooks-cc/sdk';
3
3
 
4
4
  /** Register all webhook tools on an MCP server instance. */
5
5
  declare function registerTools(server: McpServer, client: WebhooksCC): void;
6
+ /**
7
+ * Agent self-registration tools (auth.md). These are UNAUTHENTICATED — they let
8
+ * an agent that does NOT yet have a webhooks.cc credential obtain one, then
9
+ * configure the MCP server with the returned key (WHK_API_KEY). Registered on
10
+ * every server so a probing agent can always discover the on-ramp via
11
+ * `describe` / `how_to_register`.
12
+ */
13
+ declare function registerAgentRegistrationTools(server: McpServer): void;
6
14
 
7
15
  declare function registerPrompts(server: McpServer): void;
8
16
 
@@ -30,4 +38,4 @@ interface CreateServerOptions {
30
38
  */
31
39
  declare function createServer(options?: CreateServerOptions): McpServer;
32
40
 
33
- export { type CreateServerOptions, createServer, registerPrompts, registerResources, registerTools };
41
+ export { type CreateServerOptions, createServer, registerAgentRegistrationTools, registerPrompts, registerResources, registerTools };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,14 @@ import { WebhooksCC } from '@webhooks-cc/sdk';
3
3
 
4
4
  /** Register all webhook tools on an MCP server instance. */
5
5
  declare function registerTools(server: McpServer, client: WebhooksCC): void;
6
+ /**
7
+ * Agent self-registration tools (auth.md). These are UNAUTHENTICATED — they let
8
+ * an agent that does NOT yet have a webhooks.cc credential obtain one, then
9
+ * configure the MCP server with the returned key (WHK_API_KEY). Registered on
10
+ * every server so a probing agent can always discover the on-ramp via
11
+ * `describe` / `how_to_register`.
12
+ */
13
+ declare function registerAgentRegistrationTools(server: McpServer): void;
6
14
 
7
15
  declare function registerPrompts(server: McpServer): void;
8
16
 
@@ -30,4 +38,4 @@ interface CreateServerOptions {
30
38
  */
31
39
  declare function createServer(options?: CreateServerOptions): McpServer;
32
40
 
33
- export { type CreateServerOptions, createServer, registerPrompts, registerResources, registerTools };
41
+ export { type CreateServerOptions, createServer, registerAgentRegistrationTools, registerPrompts, registerResources, registerTools };
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  createServer: () => createServer,
24
+ registerAgentRegistrationTools: () => registerAgentRegistrationTools,
24
25
  registerPrompts: () => registerPrompts,
25
26
  registerResources: () => registerResources,
26
27
  registerTools: () => registerTools
@@ -708,15 +709,17 @@ function registerTools(server, client) {
708
709
  provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
709
710
  template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
710
711
  event: import_zod2.z.string().optional().describe("Provider event or topic name"),
711
- secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
712
+ secret: import_zod2.z.string().optional().describe(
713
+ "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
714
+ )
712
715
  },
713
716
  withErrorHandling(
714
717
  async ({ slug, method, headers, body, provider, template, event, secret }) => {
715
718
  let response;
716
719
  if (provider) {
717
720
  const templateSecret = secret?.trim();
718
- if (!templateSecret) {
719
- throw new Error("send_webhook with provider templates requires a non-empty secret");
721
+ if (!templateSecret && import_sdk.TEMPLATE_METADATA[provider]?.secretRequired !== false) {
722
+ throw new Error(`send_webhook with provider "${provider}" requires a non-empty secret`);
720
723
  }
721
724
  response = await client.endpoints.sendTemplate(slug, {
722
725
  provider,
@@ -892,7 +895,7 @@ function registerTools(server, client) {
892
895
  requestId: import_zod2.z.string().describe("The captured request ID"),
893
896
  provider: import_zod2.z.enum(import_sdk.VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
894
897
  secret: import_zod2.z.string().optional().describe(
895
- "Shared signing secret. Required for non-Discord providers when doing client-side verification."
898
+ "Provider credential for client-side verification (for example a shared signing secret, Adyen hex HMAC key, or PayPal webhook ID). Required for non-Discord providers."
896
899
  ),
897
900
  publicKey: import_zod2.z.string().optional().describe("Discord application public key. Required for provider=discord."),
898
901
  url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio, Square, and HubSpot verification."),
@@ -964,7 +967,9 @@ function registerTools(server, client) {
964
967
  provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
965
968
  template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
966
969
  event: import_zod2.z.string().optional().describe("Provider event or topic name"),
967
- secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
970
+ secret: import_zod2.z.string().optional().describe(
971
+ "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
972
+ )
968
973
  },
969
974
  withErrorHandling(async ({ url, method, headers, body, provider, template, event, secret }) => {
970
975
  const response = await client.sendTo(url, {
@@ -995,7 +1000,9 @@ function registerTools(server, client) {
995
1000
  provider: import_zod2.z.enum(import_sdk.TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
996
1001
  template: import_zod2.z.string().optional().describe("Provider-specific template preset"),
997
1002
  event: import_zod2.z.string().optional().describe("Provider event or topic name"),
998
- secret: import_zod2.z.string().optional().describe("Signing secret. Required when provider is set.")
1003
+ secret: import_zod2.z.string().optional().describe(
1004
+ "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
1005
+ )
999
1006
  },
1000
1007
  withErrorHandling(async ({ url, method, headers, body, provider, template, event, secret }) => {
1001
1008
  const preview = await client.buildRequest(url, {
@@ -1071,9 +1078,9 @@ function registerTools(server, client) {
1071
1078
  }
1072
1079
  if (provider) {
1073
1080
  const templateSecret = secret?.trim();
1074
- if (!templateSecret) {
1081
+ if (!templateSecret && import_sdk.TEMPLATE_METADATA[provider]?.secretRequired !== false) {
1075
1082
  throw new Error(
1076
- "test_webhook_flow with provider templates requires a non-empty secret"
1083
+ `test_webhook_flow with provider "${provider}" requires a non-empty secret`
1077
1084
  );
1078
1085
  }
1079
1086
  flow.sendTemplate({
@@ -1087,6 +1094,11 @@ function registerTools(server, client) {
1087
1094
  "test_webhook_flow cannot verify Discord signatures (Ed25519 requires a public key, not a secret)"
1088
1095
  );
1089
1096
  }
1097
+ if (!templateSecret) {
1098
+ throw new Error(
1099
+ `test_webhook_flow cannot verify "${provider}" signatures \u2014 this provider's templates are unsigned`
1100
+ );
1101
+ }
1090
1102
  flow.verifySignature({
1091
1103
  provider,
1092
1104
  secret: templateSecret
@@ -1124,24 +1136,127 @@ function registerTools(server, client) {
1124
1136
  return jsonContent(description);
1125
1137
  })
1126
1138
  );
1139
+ registerAgentRegistrationTools(server);
1140
+ }
1141
+ function resolveBaseUrl() {
1142
+ return process.env.WHK_BASE_URL ?? "https://webhooks.cc";
1143
+ }
1144
+ function registerAgentRegistrationTools(server) {
1145
+ const baseUrl = resolveBaseUrl();
1146
+ server.tool(
1147
+ "how_to_register",
1148
+ "Explain how an agent self-registers for a webhooks.cc API credential (auth.md). Call this FIRST when you have no API key. Returns the three registration flows and the auth.md documentation URL. No authentication required.",
1149
+ {},
1150
+ withErrorHandling(async () => {
1151
+ return jsonContent({
1152
+ ...import_sdk.WebhooksCC.describeRegistration(baseUrl),
1153
+ next_steps: [
1154
+ "Easiest: call register_agent (anonymous) to get a key immediately, then have a human run claim_agent with the returned userCode while logged in to webhooks.cc.",
1155
+ "Or set WHK_API_KEY to an existing whcc_ key and use the authenticated tools."
1156
+ ]
1157
+ });
1158
+ })
1159
+ );
1160
+ server.tool(
1161
+ "register_agent",
1162
+ "Self-register for a webhooks.cc API credential via the anonymous auth.md flow. Returns a whcc_ API key that works immediately in a bounded sandbox, plus a short userCode and claimUrl a human uses to bind the key to their account. No authentication required. After registering, set WHK_API_KEY to the returned credential.",
1163
+ {
1164
+ clientName: import_zod2.z.string().optional().describe("A human-readable name for this agent, recorded on the issued key")
1165
+ },
1166
+ withErrorHandling(async ({ clientName }) => {
1167
+ const reg = await import_sdk.WebhooksCC.register.anonymous({ baseUrl, clientName });
1168
+ return jsonContent({
1169
+ credential: reg.credential,
1170
+ scopes: reg.scopes,
1171
+ claim: {
1172
+ userCode: reg.userCode,
1173
+ claimUrl: reg.claimUrl,
1174
+ claimToken: reg.claimToken,
1175
+ expiresAt: reg.claimTokenExpires,
1176
+ instructions: `Ask a human to open ${reg.claimUrl} while logged in to webhooks.cc and either enter the code ${reg.userCode} or open ${reg.claimUrl}?token=${reg.claimToken}. Then call check_claim with the claimToken.`
1177
+ },
1178
+ usage: "Set WHK_API_KEY to `credential` to use the authenticated tools. Before it is claimed the key can create ephemeral endpoints and read its own captured requests in the sandbox."
1179
+ });
1180
+ })
1181
+ );
1182
+ server.tool(
1183
+ "check_claim",
1184
+ "Check whether an anonymous registration's API key has been claimed by a human yet. Poll this after register_agent until status is 'claimed'. No authentication required.",
1185
+ {
1186
+ claimToken: import_zod2.z.string().describe("The claimToken returned by register_agent")
1187
+ },
1188
+ withErrorHandling(async ({ claimToken }) => {
1189
+ const poll = await import_sdk.WebhooksCC.register.pollClaim(claimToken, { baseUrl });
1190
+ return jsonContent(poll);
1191
+ })
1192
+ );
1193
+ server.tool(
1194
+ "register_agent_with_email",
1195
+ "Self-register via the verified_email auth.md flow: webhooks.cc emails a one-time code to the address. The credential is WITHHELD until the code is confirmed with verify_agent_otp. Returns a claimToken to pass to verify_agent_otp. No authentication required.",
1196
+ {
1197
+ email: import_zod2.z.string().email({ message: "Invalid email address" }).describe("The email address to verify and bind the credential to"),
1198
+ clientName: import_zod2.z.string().optional().describe("A human-readable name for this agent, recorded on the issued key")
1199
+ },
1200
+ withErrorHandling(async ({ email, clientName }) => {
1201
+ const challenge = await import_sdk.WebhooksCC.register.withEmail(email, { baseUrl, clientName });
1202
+ return jsonContent({
1203
+ claimToken: challenge.claimToken,
1204
+ expiresAt: challenge.claimTokenExpires,
1205
+ postClaimScopes: challenge.postClaimScopes,
1206
+ 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.`
1207
+ });
1208
+ })
1209
+ );
1210
+ server.tool(
1211
+ "verify_agent_otp",
1212
+ "Complete the verified_email flow: submit the emailed OTP with the claimToken from register_agent_with_email. On success returns the whcc_ API key (bound to the verified email). Set WHK_API_KEY to it. No authentication required.",
1213
+ {
1214
+ claimToken: import_zod2.z.string().describe("The claimToken returned by register_agent_with_email"),
1215
+ otp: import_zod2.z.string().describe("The one-time code the human received by email")
1216
+ },
1217
+ withErrorHandling(async ({ claimToken, otp }) => {
1218
+ const issued = await import_sdk.WebhooksCC.register.confirmEmailOtp({ claimToken, otp }, { baseUrl });
1219
+ return jsonContent({
1220
+ credential: issued.credential,
1221
+ scopes: issued.scopes,
1222
+ usage: "Set WHK_API_KEY to `credential` to use the authenticated tools."
1223
+ });
1224
+ })
1225
+ );
1226
+ server.tool(
1227
+ "register_agent_with_idjag",
1228
+ "Self-register via the identity_assertion (ID-JAG) auth.md flow: present a verified identity-assertion JWT (urn:ietf:params:oauth:token-type:id-jag) from a trusted provider. The credential is returned synchronously \u2014 no human claim step. No webhooks.cc authentication required, but only works if webhooks.cc trusts your provider.",
1229
+ {
1230
+ assertion: import_zod2.z.string().describe("The ID-JAG assertion JWT (typ oauth-id-jag+jwt) from a trusted provider")
1231
+ },
1232
+ withErrorHandling(async ({ assertion }) => {
1233
+ const issued = await import_sdk.WebhooksCC.register.withIdJag(assertion, { baseUrl });
1234
+ return jsonContent({
1235
+ credential: issued.credential,
1236
+ scopes: issued.scopes,
1237
+ usage: "Set WHK_API_KEY to `credential` to use the authenticated tools."
1238
+ });
1239
+ })
1240
+ );
1127
1241
  }
1128
1242
 
1129
1243
  // src/index.ts
1130
- var VERSION = true ? "1.5.0" : "0.0.0-dev";
1244
+ var VERSION = true ? "1.7.0" : "0.0.0-dev";
1131
1245
  function createServer(options = {}) {
1132
1246
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
1247
+ const server = new import_mcp2.McpServer({
1248
+ name: "webhooks-cc",
1249
+ version: VERSION
1250
+ });
1133
1251
  if (!apiKey) {
1134
- throw new Error("Missing API key. Set WHK_API_KEY environment variable or pass apiKey option.");
1252
+ registerAgentRegistrationTools(server);
1253
+ return server;
1135
1254
  }
1136
1255
  const client = new import_sdk2.WebhooksCC({
1137
1256
  apiKey,
1138
1257
  webhookUrl: options.webhookUrl ?? process.env.WHK_WEBHOOK_URL,
1139
1258
  baseUrl: options.baseUrl ?? process.env.WHK_BASE_URL
1140
1259
  });
1141
- const server = new import_mcp2.McpServer({
1142
- name: "webhooks-cc",
1143
- version: VERSION
1144
- });
1145
1260
  registerTools(server, client);
1146
1261
  registerPrompts(server);
1147
1262
  registerResources(server, client);
@@ -1150,6 +1265,7 @@ function createServer(options = {}) {
1150
1265
  // Annotate the CommonJS export names for ESM import in node:
1151
1266
  0 && (module.exports = {
1152
1267
  createServer,
1268
+ registerAgentRegistrationTools,
1153
1269
  registerPrompts,
1154
1270
  registerResources,
1155
1271
  registerTools
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 } from "@webhooks-cc/sdk";
3
+ import { WebhooksCC as WebhooksCC2 } from "@webhooks-cc/sdk";
4
4
 
5
5
  // src/prompts.ts
6
6
  import { z } from "zod";
@@ -250,11 +250,13 @@ import {
250
250
  MAX_GLOB_PATTERN_LEN,
251
251
  NotFoundError,
252
252
  RateLimitError,
253
+ TEMPLATE_METADATA,
253
254
  TEMPLATE_PROVIDERS,
254
255
  TimeoutError,
255
256
  UnauthorizedError,
256
257
  VERIFY_PROVIDERS,
257
258
  verifySignature,
259
+ WebhooksCC,
258
260
  WebhooksCCError
259
261
  } from "@webhooks-cc/sdk";
260
262
  var MAX_BODY_SIZE = 32768;
@@ -701,15 +703,17 @@ function registerTools(server, client) {
701
703
  provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template to send with signed headers"),
702
704
  template: z2.string().optional().describe("Provider-specific template preset"),
703
705
  event: z2.string().optional().describe("Provider event or topic name"),
704
- secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
706
+ secret: z2.string().optional().describe(
707
+ "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
708
+ )
705
709
  },
706
710
  withErrorHandling(
707
711
  async ({ slug, method, headers, body, provider, template, event, secret }) => {
708
712
  let response;
709
713
  if (provider) {
710
714
  const templateSecret = secret?.trim();
711
- if (!templateSecret) {
712
- throw new Error("send_webhook with provider templates requires a non-empty secret");
715
+ if (!templateSecret && TEMPLATE_METADATA[provider]?.secretRequired !== false) {
716
+ throw new Error(`send_webhook with provider "${provider}" requires a non-empty secret`);
713
717
  }
714
718
  response = await client.endpoints.sendTemplate(slug, {
715
719
  provider,
@@ -885,7 +889,7 @@ function registerTools(server, client) {
885
889
  requestId: z2.string().describe("The captured request ID"),
886
890
  provider: z2.enum(VERIFY_PROVIDERS).optional().describe("Provider whose signature scheme should be verified. Omit to use stored result."),
887
891
  secret: z2.string().optional().describe(
888
- "Shared signing secret. Required for non-Discord providers when doing client-side verification."
892
+ "Provider credential for client-side verification (for example a shared signing secret, Adyen hex HMAC key, or PayPal webhook ID). Required for non-Discord providers."
889
893
  ),
890
894
  publicKey: z2.string().optional().describe("Discord application public key. Required for provider=discord."),
891
895
  url: httpUrlSchema.optional().describe("Original signed URL. Required for Twilio, Square, and HubSpot verification."),
@@ -957,7 +961,9 @@ function registerTools(server, client) {
957
961
  provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
958
962
  template: z2.string().optional().describe("Provider-specific template preset"),
959
963
  event: z2.string().optional().describe("Provider event or topic name"),
960
- secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
964
+ secret: z2.string().optional().describe(
965
+ "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
966
+ )
961
967
  },
962
968
  withErrorHandling(async ({ url, method, headers, body, provider, template, event, secret }) => {
963
969
  const response = await client.sendTo(url, {
@@ -988,7 +994,9 @@ function registerTools(server, client) {
988
994
  provider: z2.enum(TEMPLATE_PROVIDERS).optional().describe("Optional provider template for signing"),
989
995
  template: z2.string().optional().describe("Provider-specific template preset"),
990
996
  event: z2.string().optional().describe("Provider event or topic name"),
991
- secret: z2.string().optional().describe("Signing secret. Required when provider is set.")
997
+ secret: z2.string().optional().describe(
998
+ "Signing secret. Required for signed provider templates; omit for secretless providers (sendgrid, discord, plaid)."
999
+ )
992
1000
  },
993
1001
  withErrorHandling(async ({ url, method, headers, body, provider, template, event, secret }) => {
994
1002
  const preview = await client.buildRequest(url, {
@@ -1064,9 +1072,9 @@ function registerTools(server, client) {
1064
1072
  }
1065
1073
  if (provider) {
1066
1074
  const templateSecret = secret?.trim();
1067
- if (!templateSecret) {
1075
+ if (!templateSecret && TEMPLATE_METADATA[provider]?.secretRequired !== false) {
1068
1076
  throw new Error(
1069
- "test_webhook_flow with provider templates requires a non-empty secret"
1077
+ `test_webhook_flow with provider "${provider}" requires a non-empty secret`
1070
1078
  );
1071
1079
  }
1072
1080
  flow.sendTemplate({
@@ -1080,6 +1088,11 @@ function registerTools(server, client) {
1080
1088
  "test_webhook_flow cannot verify Discord signatures (Ed25519 requires a public key, not a secret)"
1081
1089
  );
1082
1090
  }
1091
+ if (!templateSecret) {
1092
+ throw new Error(
1093
+ `test_webhook_flow cannot verify "${provider}" signatures \u2014 this provider's templates are unsigned`
1094
+ );
1095
+ }
1083
1096
  flow.verifySignature({
1084
1097
  provider,
1085
1098
  secret: templateSecret
@@ -1117,24 +1130,127 @@ function registerTools(server, client) {
1117
1130
  return jsonContent(description);
1118
1131
  })
1119
1132
  );
1133
+ registerAgentRegistrationTools(server);
1134
+ }
1135
+ function resolveBaseUrl() {
1136
+ return process.env.WHK_BASE_URL ?? "https://webhooks.cc";
1137
+ }
1138
+ function registerAgentRegistrationTools(server) {
1139
+ const baseUrl = resolveBaseUrl();
1140
+ server.tool(
1141
+ "how_to_register",
1142
+ "Explain how an agent self-registers for a webhooks.cc API credential (auth.md). Call this FIRST when you have no API key. Returns the three registration flows and the auth.md documentation URL. No authentication required.",
1143
+ {},
1144
+ withErrorHandling(async () => {
1145
+ return jsonContent({
1146
+ ...WebhooksCC.describeRegistration(baseUrl),
1147
+ next_steps: [
1148
+ "Easiest: call register_agent (anonymous) to get a key immediately, then have a human run claim_agent with the returned userCode while logged in to webhooks.cc.",
1149
+ "Or set WHK_API_KEY to an existing whcc_ key and use the authenticated tools."
1150
+ ]
1151
+ });
1152
+ })
1153
+ );
1154
+ server.tool(
1155
+ "register_agent",
1156
+ "Self-register for a webhooks.cc API credential via the anonymous auth.md flow. Returns a whcc_ API key that works immediately in a bounded sandbox, plus a short userCode and claimUrl a human uses to bind the key to their account. No authentication required. After registering, set WHK_API_KEY to the returned credential.",
1157
+ {
1158
+ clientName: z2.string().optional().describe("A human-readable name for this agent, recorded on the issued key")
1159
+ },
1160
+ withErrorHandling(async ({ clientName }) => {
1161
+ const reg = await WebhooksCC.register.anonymous({ baseUrl, clientName });
1162
+ return jsonContent({
1163
+ credential: reg.credential,
1164
+ scopes: reg.scopes,
1165
+ claim: {
1166
+ userCode: reg.userCode,
1167
+ claimUrl: reg.claimUrl,
1168
+ claimToken: reg.claimToken,
1169
+ expiresAt: reg.claimTokenExpires,
1170
+ instructions: `Ask a human to open ${reg.claimUrl} while logged in to webhooks.cc and either enter the code ${reg.userCode} or open ${reg.claimUrl}?token=${reg.claimToken}. Then call check_claim with the claimToken.`
1171
+ },
1172
+ usage: "Set WHK_API_KEY to `credential` to use the authenticated tools. Before it is claimed the key can create ephemeral endpoints and read its own captured requests in the sandbox."
1173
+ });
1174
+ })
1175
+ );
1176
+ server.tool(
1177
+ "check_claim",
1178
+ "Check whether an anonymous registration's API key has been claimed by a human yet. Poll this after register_agent until status is 'claimed'. No authentication required.",
1179
+ {
1180
+ claimToken: z2.string().describe("The claimToken returned by register_agent")
1181
+ },
1182
+ withErrorHandling(async ({ claimToken }) => {
1183
+ const poll = await WebhooksCC.register.pollClaim(claimToken, { baseUrl });
1184
+ return jsonContent(poll);
1185
+ })
1186
+ );
1187
+ server.tool(
1188
+ "register_agent_with_email",
1189
+ "Self-register via the verified_email auth.md flow: webhooks.cc emails a one-time code to the address. The credential is WITHHELD until the code is confirmed with verify_agent_otp. Returns a claimToken to pass to verify_agent_otp. No authentication required.",
1190
+ {
1191
+ email: z2.string().email({ message: "Invalid email address" }).describe("The email address to verify and bind the credential to"),
1192
+ clientName: z2.string().optional().describe("A human-readable name for this agent, recorded on the issued key")
1193
+ },
1194
+ withErrorHandling(async ({ email, clientName }) => {
1195
+ const challenge = await WebhooksCC.register.withEmail(email, { baseUrl, clientName });
1196
+ return jsonContent({
1197
+ claimToken: challenge.claimToken,
1198
+ expiresAt: challenge.claimTokenExpires,
1199
+ postClaimScopes: challenge.postClaimScopes,
1200
+ 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.`
1201
+ });
1202
+ })
1203
+ );
1204
+ server.tool(
1205
+ "verify_agent_otp",
1206
+ "Complete the verified_email flow: submit the emailed OTP with the claimToken from register_agent_with_email. On success returns the whcc_ API key (bound to the verified email). Set WHK_API_KEY to it. No authentication required.",
1207
+ {
1208
+ claimToken: z2.string().describe("The claimToken returned by register_agent_with_email"),
1209
+ otp: z2.string().describe("The one-time code the human received by email")
1210
+ },
1211
+ withErrorHandling(async ({ claimToken, otp }) => {
1212
+ const issued = await WebhooksCC.register.confirmEmailOtp({ claimToken, otp }, { baseUrl });
1213
+ return jsonContent({
1214
+ credential: issued.credential,
1215
+ scopes: issued.scopes,
1216
+ usage: "Set WHK_API_KEY to `credential` to use the authenticated tools."
1217
+ });
1218
+ })
1219
+ );
1220
+ server.tool(
1221
+ "register_agent_with_idjag",
1222
+ "Self-register via the identity_assertion (ID-JAG) auth.md flow: present a verified identity-assertion JWT (urn:ietf:params:oauth:token-type:id-jag) from a trusted provider. The credential is returned synchronously \u2014 no human claim step. No webhooks.cc authentication required, but only works if webhooks.cc trusts your provider.",
1223
+ {
1224
+ assertion: z2.string().describe("The ID-JAG assertion JWT (typ oauth-id-jag+jwt) from a trusted provider")
1225
+ },
1226
+ withErrorHandling(async ({ assertion }) => {
1227
+ const issued = await WebhooksCC.register.withIdJag(assertion, { baseUrl });
1228
+ return jsonContent({
1229
+ credential: issued.credential,
1230
+ scopes: issued.scopes,
1231
+ usage: "Set WHK_API_KEY to `credential` to use the authenticated tools."
1232
+ });
1233
+ })
1234
+ );
1120
1235
  }
1121
1236
 
1122
1237
  // src/index.ts
1123
- var VERSION = true ? "1.5.0" : "0.0.0-dev";
1238
+ var VERSION = true ? "1.7.0" : "0.0.0-dev";
1124
1239
  function createServer(options = {}) {
1125
1240
  const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
1241
+ const server = new McpServer2({
1242
+ name: "webhooks-cc",
1243
+ version: VERSION
1244
+ });
1126
1245
  if (!apiKey) {
1127
- throw new Error("Missing API key. Set WHK_API_KEY environment variable or pass apiKey option.");
1246
+ registerAgentRegistrationTools(server);
1247
+ return server;
1128
1248
  }
1129
- const client = new WebhooksCC({
1249
+ const client = new WebhooksCC2({
1130
1250
  apiKey,
1131
1251
  webhookUrl: options.webhookUrl ?? process.env.WHK_WEBHOOK_URL,
1132
1252
  baseUrl: options.baseUrl ?? process.env.WHK_BASE_URL
1133
1253
  });
1134
- const server = new McpServer2({
1135
- name: "webhooks-cc",
1136
- version: VERSION
1137
- });
1138
1254
  registerTools(server, client);
1139
1255
  registerPrompts(server);
1140
1256
  registerResources(server, client);
@@ -1142,6 +1258,7 @@ function createServer(options = {}) {
1142
1258
  }
1143
1259
  export {
1144
1260
  createServer,
1261
+ registerAgentRegistrationTools,
1145
1262
  registerPrompts,
1146
1263
  registerResources,
1147
1264
  registerTools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webhooks-cc/mcp",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "MCP server for webhooks.cc — AI agent integration for webhook testing",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -36,15 +36,15 @@
36
36
  },
37
37
  "homepage": "https://webhooks.cc",
38
38
  "dependencies": {
39
- "@modelcontextprotocol/sdk": "^1.29.0",
39
+ "@modelcontextprotocol/sdk": "^1.30.0",
40
40
  "zod": "^4.4.3",
41
- "@webhooks-cc/sdk": "1.7.0"
41
+ "@webhooks-cc/sdk": "1.9.1"
42
42
  },
43
43
  "devDependencies": {
44
- "@types/node": "^25.9.1",
44
+ "@types/node": "^26.2.0",
45
45
  "tsup": "^8.5.1",
46
46
  "typescript": "^6.0.3",
47
- "vitest": "^4.1.7"
47
+ "vitest": "^4.1.10"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsup",