revclamp-mcp 1.20.9 → 1.20.11

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
@@ -5,18 +5,18 @@
5
5
  **`revclamp-mcp`** is a standalone, zero-dependency **Open-Source Model Context Protocol (MCP) Server & CLI** (`index.mjs` + `local_engine.mjs`) implementing **9 deterministic B2B FinOps, Stripe `2025-03-31.basil`, Visa CE 3.0, EU/XI VIES VAT, Shopify Plus B2B Net-15/30/60, and Webhook Security tools** in pure Node.js.
6
6
 
7
7
  - **100% Local In-Process Execution (Default — Zero Network / Zero API Key Required):** All 9 MCP tools (`initialize`, `tools/list`, and `tools/call`) execute locally in-process inside [`local_engine.mjs`](./local_engine.mjs) using pure Node.js (`node:crypto` HMAC-SHA256 verification, integer-cent double-entry accounting, 72-hour webhook replay state machines, and `18,000` UTF-16 code unit CE 3.0 linting). Zero network calls are ever made unless `REVCLAMP_API_KEY` or `REVCLAMP_MCP_URL` is explicitly configured.
8
- - **Optional Hosted Cloud Workspace Sync (`REVCLAMP_API_KEY`):** When `REVCLAMP_API_KEY` (`rg_live_...`) is configured, `revclamp-mcp` connects to [**RevClamp Enterprise**](https://revclamp.com) (`https://api.revclamp.com/api/mcp`) to unlock the **90-day SQLite WAL usage & audit ledger**, **24/7 autonomous Stripe dispute & entitlement webhook outbox**, and **real-time EU Commission VIES SOAP gateway consultation numbers**.
8
+ - **Optional Hosted Cloud Workspace Sync (`REVCLAMP_API_KEY`):** When `REVCLAMP_API_KEY` (`rg_live_...`) is configured, `revclamp-mcp` connects to [**RevClamp Enterprise**](https://revclamp.com) (`https://api.revclamp.com/api/mcp`) to unlock the **90-day durable transactional usage & audit ledger**, **24/7 autonomous Stripe dispute & entitlement webhook outbox**, and **real-time EU Commission VIES SOAP gateway consultation numbers**.
9
9
 
10
10
  ## Local Open-Core (`local_engine.mjs`) vs. Hosted Cloud (`REVCLAMP_API_KEY`)
11
11
 
12
12
  | Capability | Local Open-Core Default (Zero API Key) | Hosted Cloud (`REVCLAMP_API_KEY`) |
13
13
  | :--- | :--- | :--- |
14
14
  | **Execution & Network** | 100% in-process (`local_engine.mjs`), zero network calls | HTTPS JSON-RPC to `https://api.revclamp.com/api/mcp` (`redirect: "error"`) |
15
- | **Usage & Audit Storage** | Stateless evaluation of caller-supplied JSON snapshots | **Persistent 90-day SQLite WAL ledger** + Stripe historical backfill |
15
+ | **Usage & Audit Storage** | Stateless evaluation of caller-supplied JSON snapshots | **Persistent 90-day durable transactional ledger** + Stripe historical backfill |
16
16
  | **Dispute & Entitlement Action** | Returns non-destructive `submit: false` draft & `revoke_entitlement` decision | **24/7 autonomous Stripe webhook outbox** (`disputes.update` & signed revoke webhooks) |
17
17
  | **EU & `XI` B2B VAT (`VIES`)** | Deterministic syntax & `GR -> EL` normalization (`FORMAT_ONLY` default, `valid: null`) | **Live EU Commission VIES SOAP consultation IDs** + circuit-breaker retry queue (`viesMode: "LIVE"` + `REVCLAMP_API_KEY`) |
18
18
  | **Monthly Event Tiers** | Unlimited local in-process evaluations (0 network cost) | **Free Sandbox** (`5,000` events/mo) · **Starter $19/mo** (`100k`) · **Growth $59/mo** (`1M`) · **Scale $199/mo** (`5M`) |
19
- | **Data Privacy (`INV-SAAS-04`)** | Nothing ever leaves your machine | Only explicit `tools/call` arguments are sent over TLS after local live-secret scrubbing (`scanMcpClientPayloadForSecrets` in `index.mjs` blocks `sk_live_`, non-test `whsec_`, `shpat_` locally) |
19
+ | **Data Minimization & Privacy** | Nothing ever leaves your machine | Only explicit `tools/call` arguments are sent over TLS after local live-secret scrubbing (blocks `sk_live_`, non-test `whsec_`, and `shpat_` locally before any network request) |
20
20
 
21
21
  ## Open-Source MCP Tools Implemented in `local_engine.mjs` (9)
22
22
 
@@ -59,7 +59,13 @@ curl -sS -X POST https://api.revclamp.com/api/mcp \
59
59
 
60
60
  ```bash
61
61
  # Run a <60s local CLI smoke check (verifies all 9 tool schemas & executes a local_engine.mjs sample audit with zero API key)
62
- npx -y github:lhphong09t2/revclamp-mcp#v1.20.9 --self-test
62
+ npx -y github:lhphong09t2/revclamp-mcp#v1.20.11 --self-test
63
+
64
+ # Provision a free Cloud Workspace (or reuse REVCLAMP_API_KEY) & send a 6-digit login OTP
65
+ npx -y revclamp-mcp@1.20.11 --init-workspace you@company.com
66
+
67
+ # Verify the 6-digit OTP using REVCLAMP_API_KEY from environment
68
+ REVCLAMP_API_KEY="rg_live_..." npx -y revclamp-mcp@1.20.11 --verify-otp you@company.com 123456
63
69
 
64
70
  # List all 9 MCP tools and JSON schemas (runs locally in-process)
65
71
  node index.mjs --list-tools
package/index.mjs CHANGED
@@ -33,10 +33,16 @@ export {
33
33
  executeLocalMcpJsonRpc,
34
34
  };
35
35
 
36
- export const PACKAGE_VERSION = "1.20.9";
36
+ export const PACKAGE_VERSION = "1.20.11";
37
37
  export const DEFAULT_MCP_ENDPOINT = "https://api.revclamp.com/api/mcp";
38
38
  export const ALLOWED_MCP_ORIGINS = Object.freeze(
39
- new Set(["https://api.revclamp.com", "https://revclamp.com"])
39
+ new Set([
40
+ "https://api.revclamp.com",
41
+ "https://revclamp.com",
42
+ "https://app.revclamp.com",
43
+ "http://127.0.0.1",
44
+ "http://localhost",
45
+ ])
40
46
  );
41
47
  export const MAX_PAYLOAD_BYTES = 32_768;
42
48
  export const MAX_STDIO_LINE_BYTES = MAX_PAYLOAD_BYTES;
@@ -371,13 +377,19 @@ export async function forwardMcpJsonRpc(
371
377
  rpcRequest?.params?.name === "revclamp_validate_eu_vat_vies" &&
372
378
  (rawViesMode === "FORMAT_ONLY" || rpcRequest?.params?.arguments?.demo === true);
373
379
 
380
+ const cleanKey = String(apiKey || "").trim();
381
+
374
382
  if (
375
383
  String(mode || "").toLowerCase() === "local" ||
376
384
  !hasExplicitRemote ||
377
385
  isLocalOnlyViesCall
378
386
  ) {
379
387
  if (isNotification) return null;
380
- return executeLocalMcpJsonRpc({ ...rpcRequest, id: safeId }, PACKAGE_VERSION);
388
+ return executeLocalMcpJsonRpc(
389
+ { ...rpcRequest, id: safeId },
390
+ PACKAGE_VERSION,
391
+ { suppressCloudUpgradeHint: Boolean(cleanKey) }
392
+ );
381
393
  }
382
394
 
383
395
  const boundedTimeoutMs = Math.min(10_000, Math.max(100, Number(timeoutMs) || REQUEST_TIMEOUT_MS));
@@ -385,7 +397,6 @@ export async function forwardMcpJsonRpc(
385
397
  "content-type": "application/json",
386
398
  accept: "application/json",
387
399
  };
388
- const cleanKey = String(apiKey || "").trim();
389
400
  if (cleanKey) {
390
401
  headers.authorization = `Bearer ${cleanKey}`;
391
402
  }
@@ -788,6 +799,9 @@ function printHelp() {
788
799
  " revclamp-mcp --sim-replay [presetKey] [windowSec] Simulate webhook replay & out-of-order state machine",
789
800
  " revclamp-mcp --lint-evidence [preset] Lint Stripe dispute evidence UTF-16 & CE 3.0 completeness",
790
801
  " revclamp-mcp --verify-webhook [provider] [rawPayload] [signatureHeader] Verify webhook HMAC via REVCLAMP_WEBHOOK_SECRET env var",
802
+ " revclamp-mcp --init-workspace <email> Provision a free Cloud Workspace (or reuse REVCLAMP_API_KEY) & send 6-digit OTP",
803
+ " revclamp-mcp --resend-otp <email> Resend 6-digit OTP using existing REVCLAMP_API_KEY env var",
804
+ " revclamp-mcp --verify-otp <email> <code> Verify 6-digit OTP using REVCLAMP_API_KEY env var & print MCP config snippet",
791
805
  " revclamp-mcp --call <tool_name> '<json_args>' Invoke any RevClamp MCP tool directly",
792
806
  "",
793
807
  "Environment Variables:",
@@ -810,17 +824,468 @@ const KNOWN_CLI_FLAGS = new Set([
810
824
  "--sim-replay",
811
825
  "--lint-evidence",
812
826
  "--verify-webhook",
827
+ "--init-workspace",
828
+ "--resend-otp",
829
+ "--verify-otp",
813
830
  "--call",
814
831
  "--stdio",
815
832
  "--url",
816
833
  "--endpoint",
817
834
  ]);
818
835
 
836
+ function resolveValidatedOnboardingOrigin(rawBaseUrl = "https://revclamp.com") {
837
+ const trimmed = String(rawBaseUrl || "https://revclamp.com").trim();
838
+ let parsed;
839
+ try {
840
+ parsed = new URL(trimmed);
841
+ } catch {
842
+ return { ok: false, errorCode: "INVALID_MCP_ENDPOINT_URL" };
843
+ }
844
+ if (parsed.username || parsed.password) {
845
+ return { ok: false, errorCode: "UNSAFE_MCP_ENDPOINT_CREDENTIALS" };
846
+ }
847
+ const baseOriginWithoutPort = `${parsed.protocol}//${parsed.hostname}`;
848
+ const isLoopbackHttp =
849
+ parsed.protocol === "http:" &&
850
+ (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost") &&
851
+ ALLOWED_MCP_ORIGINS.has(baseOriginWithoutPort);
852
+ const isAllowedHttps =
853
+ parsed.protocol === "https:" && ALLOWED_MCP_ORIGINS.has(parsed.origin);
854
+ if (!isAllowedHttps && !isLoopbackHttp) {
855
+ return {
856
+ ok: false,
857
+ errorCode: "UNTRUSTED_MCP_ENDPOINT_ORIGIN",
858
+ origin: parsed.origin,
859
+ };
860
+ }
861
+ return { ok: true, origin: parsed.origin };
862
+ }
863
+
864
+ /**
865
+ * Executes CLI-to-Cloud workspace onboarding commands (--init-workspace, --resend-otp, --verify-otp)
866
+ * enforcing S1 (idempotent key reuse), S2 (partial-failure key preservation), and S3 (zero raw secret in argv).
867
+ *
868
+ * @param {object} params
869
+ * @returns {Promise<object>}
870
+ */
871
+ export async function runMcpCliOnboardingCommand({
872
+ mode = "",
873
+ email = "",
874
+ otpCode = "",
875
+ argv = [],
876
+ env = process.env,
877
+ baseUrl = "",
878
+ fetchImpl,
879
+ } = {}) {
880
+ const scanTokens = [
881
+ ...(Array.isArray(argv) ? argv : []),
882
+ email,
883
+ otpCode,
884
+ ].map((t) => String(t ?? "").trim());
885
+
886
+ for (const tok of scanTokens) {
887
+ if (
888
+ /^rg_(?:live|test)_/i.test(tok) ||
889
+ /(?:^|=)rg_(?:live|test)_/i.test(tok) ||
890
+ tok === "--api-key" ||
891
+ tok.startsWith("--api-key=")
892
+ ) {
893
+ return {
894
+ ok: false,
895
+ exitCode: 1,
896
+ errorCode: "RAW_API_KEY_IN_ARGV_FORBIDDEN",
897
+ message:
898
+ "Pass REVCLAMP_API_KEY via environment variable, never as a CLI argument.",
899
+ };
900
+ }
901
+ }
902
+
903
+ const effectiveEnv = env && typeof env === "object" ? env : process.env;
904
+ const resolvedBaseUrl =
905
+ baseUrl ||
906
+ effectiveEnv.REVCLAMP_API_URL ||
907
+ effectiveEnv.REVCLAMP_MCP_URL ||
908
+ effectiveEnv.REVCLAMP_WEB_URL ||
909
+ effectiveEnv.REVCLAMP_ONBOARDING_URL ||
910
+ "https://revclamp.com";
911
+ const originCheck = resolveValidatedOnboardingOrigin(resolvedBaseUrl);
912
+ if (!originCheck.ok) {
913
+ return {
914
+ ok: false,
915
+ exitCode: 1,
916
+ errorCode: originCheck.errorCode,
917
+ message: `Untrusted or invalid onboarding origin: ${String(resolvedBaseUrl)}`,
918
+ };
919
+ }
920
+
921
+ const cleanEmail = typeof email === "string" ? email.trim().toLowerCase() : "";
922
+ if (
923
+ cleanEmail.length < 3 ||
924
+ cleanEmail.length > 254 ||
925
+ !/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$/.test(cleanEmail)
926
+ ) {
927
+ return {
928
+ ok: false,
929
+ exitCode: 1,
930
+ errorCode: "INVALID_EMAIL_ADDRESS",
931
+ message: "A valid email address (3..254 characters) is required.",
932
+ };
933
+ }
934
+
935
+ const normalizedMode = String(mode || "")
936
+ .trim()
937
+ .replace(/^--+/, "")
938
+ .toLowerCase();
939
+ const effectiveFetch = fetchImpl || globalThis.fetch;
940
+
941
+ const postOnboardingJson = async (pathname, payloadObj, apiKeyHeader = "") => {
942
+ const targetUrl = `${originCheck.origin}${pathname}`;
943
+ const headers = {
944
+ "content-type": "application/json",
945
+ accept: "application/json",
946
+ };
947
+ if (apiKeyHeader) {
948
+ headers.authorization = `Bearer ${apiKeyHeader}`;
949
+ headers["x-revclamp-tenant-key"] = apiKeyHeader;
950
+ }
951
+ try {
952
+ const res = await effectiveFetch(targetUrl, {
953
+ method: "POST",
954
+ headers,
955
+ body: JSON.stringify(payloadObj),
956
+ redirect: "error",
957
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
958
+ });
959
+ const status = Number(res?.status || 0);
960
+ const httpOk =
961
+ typeof res?.ok === "boolean" ? res.ok : status >= 200 && status < 300;
962
+ const rawText = await readBoundedResponseText(res, MAX_PAYLOAD_BYTES);
963
+ let body = null;
964
+ try {
965
+ body = rawText ? JSON.parse(rawText) : null;
966
+ } catch {
967
+ body = null;
968
+ }
969
+ return {
970
+ httpOk,
971
+ status,
972
+ body,
973
+ errorCode:
974
+ body && typeof body === "object"
975
+ ? body.errorCode || body.error || null
976
+ : null,
977
+ message:
978
+ body && typeof body === "object" ? body.message || null : null,
979
+ };
980
+ } catch (err) {
981
+ if (err instanceof Error && err.message === "UPSTREAM_RESPONSE_TOO_LARGE") {
982
+ return {
983
+ httpOk: false,
984
+ status: 502,
985
+ body: null,
986
+ errorCode: "UPSTREAM_RESPONSE_TOO_LARGE",
987
+ message: "Upstream onboarding response exceeded 32,768-byte ceiling.",
988
+ };
989
+ }
990
+ return {
991
+ httpOk: false,
992
+ status: 0,
993
+ body: null,
994
+ errorCode: "ONBOARDING_NETWORK_ERROR",
995
+ message: err instanceof Error ? err.message : "Network error",
996
+ };
997
+ }
998
+ };
999
+
1000
+ const envApiKey =
1001
+ typeof effectiveEnv.REVCLAMP_API_KEY === "string"
1002
+ ? effectiveEnv.REVCLAMP_API_KEY.trim()
1003
+ : "";
1004
+ const hasValidEnvKey = /^rg_live_[a-zA-Z0-9_-]{12,128}$/.test(envApiKey);
1005
+
1006
+ if (normalizedMode === "init-workspace" || normalizedMode === "init") {
1007
+ let activeApiKey = "";
1008
+ let tenantId = null;
1009
+ let reusedExistingEnvKey = false;
1010
+
1011
+ const provisionFreshWorkspace = async () => {
1012
+ const provRes = await postOnboardingJson("/api/dashboard/provision", {
1013
+ tier: "free",
1014
+ acquisitionSurface: "mcp_cli",
1015
+ merchantEmail: cleanEmail,
1016
+ });
1017
+ const provisionedKey =
1018
+ provRes.body && typeof provRes.body.apiKey === "string"
1019
+ ? provRes.body.apiKey.trim()
1020
+ : "";
1021
+ if (!provRes.httpOk || !provisionedKey) {
1022
+ return {
1023
+ ok: false,
1024
+ res: {
1025
+ ok: false,
1026
+ exitCode: 1,
1027
+ stage: "PROVISION_FAILED",
1028
+ errorCode: provRes.errorCode || "PROVISION_WORKSPACE_FAILED",
1029
+ status: provRes.status,
1030
+ message:
1031
+ provRes.message || "Failed to provision free Cloud Workspace.",
1032
+ },
1033
+ };
1034
+ }
1035
+ return {
1036
+ ok: true,
1037
+ apiKey: provisionedKey,
1038
+ tenantId: provRes.body.tenantId || null,
1039
+ };
1040
+ };
1041
+
1042
+ if (hasValidEnvKey) {
1043
+ activeApiKey = envApiKey;
1044
+ reusedExistingEnvKey = true;
1045
+ } else {
1046
+ const freshProv = await provisionFreshWorkspace();
1047
+ if (!freshProv.ok) {
1048
+ return freshProv.res;
1049
+ }
1050
+ activeApiKey = freshProv.apiKey;
1051
+ tenantId = freshProv.tenantId;
1052
+ }
1053
+
1054
+ let otpRes = await postOnboardingJson(
1055
+ "/api/dashboard/send-otp",
1056
+ {
1057
+ email: cleanEmail,
1058
+ purpose: "dashboard_login",
1059
+ acquisitionSurface: "mcp_cli",
1060
+ },
1061
+ activeApiKey
1062
+ );
1063
+
1064
+ if (
1065
+ reusedExistingEnvKey &&
1066
+ (otpRes.status === 401 || otpRes.errorCode === "UNAUTHORIZED_TENANT_KEY")
1067
+ ) {
1068
+ const freshProv = await provisionFreshWorkspace();
1069
+ if (!freshProv.ok) {
1070
+ return freshProv.res;
1071
+ }
1072
+ activeApiKey = freshProv.apiKey;
1073
+ tenantId = freshProv.tenantId;
1074
+ reusedExistingEnvKey = false;
1075
+ otpRes = await postOnboardingJson(
1076
+ "/api/dashboard/send-otp",
1077
+ {
1078
+ email: cleanEmail,
1079
+ purpose: "dashboard_login",
1080
+ acquisitionSurface: "mcp_cli",
1081
+ },
1082
+ activeApiKey
1083
+ );
1084
+ }
1085
+
1086
+ const cmdKeyToken = reusedExistingEnvKey
1087
+ ? '"$REVCLAMP_API_KEY"'
1088
+ : activeApiKey;
1089
+
1090
+ if (!otpRes.httpOk || otpRes.body?.ok === false) {
1091
+ return {
1092
+ ok: false,
1093
+ exitCode: 1,
1094
+ stage: "SEND_OTP_FAILED",
1095
+ errorCode: otpRes.errorCode || "SEND_OTP_FAILED",
1096
+ status: otpRes.status,
1097
+ email: cleanEmail,
1098
+ tenantId: otpRes.body?.tenantId || tenantId,
1099
+ apiKey: activeApiKey,
1100
+ reusedExistingEnvKey,
1101
+ nextStepCommand: `REVCLAMP_API_KEY=${cmdKeyToken} npx -y revclamp-mcp@${PACKAGE_VERSION} --resend-otp ${cleanEmail}`,
1102
+ verifyCommandHint: `REVCLAMP_API_KEY=${cmdKeyToken} npx -y revclamp-mcp@${PACKAGE_VERSION} --verify-otp ${cleanEmail} <6-digit-code>`,
1103
+ };
1104
+ }
1105
+
1106
+ return {
1107
+ ok: true,
1108
+ exitCode: 0,
1109
+ stage: "OTP_DISPATCHED",
1110
+ email: cleanEmail,
1111
+ tenantId: otpRes.body?.tenantId || tenantId,
1112
+ apiKey: activeApiKey,
1113
+ reusedExistingEnvKey,
1114
+ nextStepCommand: `REVCLAMP_API_KEY=${cmdKeyToken} npx -y revclamp-mcp@${PACKAGE_VERSION} --verify-otp ${cleanEmail} <6-digit-code>`,
1115
+ resendCommand: `REVCLAMP_API_KEY=${cmdKeyToken} npx -y revclamp-mcp@${PACKAGE_VERSION} --resend-otp ${cleanEmail}`,
1116
+ upgradeUrl: "https://revclamp.com/pricing?source=mcp_cli",
1117
+ };
1118
+ }
1119
+
1120
+ if (normalizedMode === "resend-otp" || normalizedMode === "resend") {
1121
+ if (!envApiKey) {
1122
+ return {
1123
+ ok: false,
1124
+ exitCode: 1,
1125
+ errorCode: "MISSING_REVCLAMP_API_KEY_ENV",
1126
+ message:
1127
+ "Set REVCLAMP_API_KEY in the environment before running --resend-otp.",
1128
+ };
1129
+ }
1130
+ if (!hasValidEnvKey) {
1131
+ return {
1132
+ ok: false,
1133
+ exitCode: 1,
1134
+ errorCode: "INVALID_REVCLAMP_API_KEY_ENV",
1135
+ message:
1136
+ "REVCLAMP_API_KEY must be a valid rg_live_ workspace key.",
1137
+ };
1138
+ }
1139
+
1140
+ const otpRes = await postOnboardingJson(
1141
+ "/api/dashboard/send-otp",
1142
+ {
1143
+ email: cleanEmail,
1144
+ purpose: "dashboard_login",
1145
+ acquisitionSurface: "mcp_cli",
1146
+ },
1147
+ envApiKey
1148
+ );
1149
+
1150
+ if (!otpRes.httpOk || otpRes.body?.ok === false) {
1151
+ return {
1152
+ ok: false,
1153
+ exitCode: 1,
1154
+ stage: "SEND_OTP_FAILED",
1155
+ errorCode: otpRes.errorCode || "SEND_OTP_FAILED",
1156
+ status: otpRes.status,
1157
+ email: cleanEmail,
1158
+ tenantId: otpRes.body?.tenantId || null,
1159
+ reusedExistingEnvKey: true,
1160
+ nextStepCommand: `REVCLAMP_API_KEY="$REVCLAMP_API_KEY" npx -y revclamp-mcp@${PACKAGE_VERSION} --resend-otp ${cleanEmail}`,
1161
+ };
1162
+ }
1163
+
1164
+ return {
1165
+ ok: true,
1166
+ exitCode: 0,
1167
+ stage: "OTP_RESENT",
1168
+ email: cleanEmail,
1169
+ tenantId: otpRes.body?.tenantId || null,
1170
+ reusedExistingEnvKey: true,
1171
+ nextStepCommand: `REVCLAMP_API_KEY="$REVCLAMP_API_KEY" npx -y revclamp-mcp@${PACKAGE_VERSION} --verify-otp ${cleanEmail} <6-digit-code>`,
1172
+ upgradeUrl: "https://revclamp.com/pricing?source=mcp_cli",
1173
+ };
1174
+ }
1175
+
1176
+ if (normalizedMode === "verify-otp" || normalizedMode === "verify") {
1177
+ if (!envApiKey) {
1178
+ return {
1179
+ ok: false,
1180
+ exitCode: 1,
1181
+ errorCode: "MISSING_REVCLAMP_API_KEY_ENV",
1182
+ message:
1183
+ "Set REVCLAMP_API_KEY in the environment before running --verify-otp.",
1184
+ };
1185
+ }
1186
+ if (!hasValidEnvKey) {
1187
+ return {
1188
+ ok: false,
1189
+ exitCode: 1,
1190
+ errorCode: "INVALID_REVCLAMP_API_KEY_ENV",
1191
+ message:
1192
+ "REVCLAMP_API_KEY must be a valid rg_live_ workspace key.",
1193
+ };
1194
+ }
1195
+
1196
+ const cleanCode =
1197
+ typeof otpCode === "string" || typeof otpCode === "number"
1198
+ ? String(otpCode).trim()
1199
+ : "";
1200
+ if (!/^\d{6}$/.test(cleanCode)) {
1201
+ return {
1202
+ ok: false,
1203
+ exitCode: 1,
1204
+ errorCode: "INVALID_OTP_CODE_FORMAT",
1205
+ message: "OTP code must be a 6-digit numeric code.",
1206
+ };
1207
+ }
1208
+
1209
+ const verifyRes = await postOnboardingJson(
1210
+ "/api/dashboard/verify-otp",
1211
+ {
1212
+ email: cleanEmail,
1213
+ otpCode: cleanCode,
1214
+ code: cleanCode,
1215
+ purpose: "dashboard_login",
1216
+ acquisitionSurface: "mcp_cli",
1217
+ },
1218
+ envApiKey
1219
+ );
1220
+
1221
+ if (
1222
+ verifyRes.status === 200 &&
1223
+ verifyRes.body?.ok === true &&
1224
+ verifyRes.body?.verified === true
1225
+ ) {
1226
+ return {
1227
+ ok: true,
1228
+ exitCode: 0,
1229
+ verified: true,
1230
+ stage: "WORKSPACE_VERIFIED",
1231
+ email: cleanEmail,
1232
+ tenantId: verifyRes.body?.tenantId || null,
1233
+ mcpServersConfig: {
1234
+ mcpServers: {
1235
+ revclamp: {
1236
+ command: "npx",
1237
+ args: ["-y", `revclamp-mcp@${PACKAGE_VERSION}`],
1238
+ env: {
1239
+ REVCLAMP_API_KEY: "<SET_FROM_ENV_RG_LIVE_KEY>",
1240
+ },
1241
+ },
1242
+ },
1243
+ },
1244
+ upgradeUrl: "https://revclamp.com/pricing?source=mcp_cli",
1245
+ };
1246
+ }
1247
+
1248
+ return {
1249
+ ok: false,
1250
+ exitCode: 1,
1251
+ verified: false,
1252
+ stage: "VERIFY_OTP_FAILED",
1253
+ errorCode: verifyRes.errorCode || "OTP_VERIFICATION_FAILED",
1254
+ status: verifyRes.status,
1255
+ message: verifyRes.message || "OTP verification failed.",
1256
+ nextStepCommand: `REVCLAMP_API_KEY="$REVCLAMP_API_KEY" npx -y revclamp-mcp@${PACKAGE_VERSION} --verify-otp ${cleanEmail} <6-digit-code>`,
1257
+ };
1258
+ }
1259
+
1260
+ return {
1261
+ ok: false,
1262
+ exitCode: 1,
1263
+ errorCode: "UNKNOWN_ONBOARDING_MODE",
1264
+ message: `Unsupported onboarding mode: ${String(mode)}`,
1265
+ };
1266
+ }
1267
+
819
1268
  export async function runCli(argv = process.argv.slice(2), options = {}) {
820
1269
  const effectiveOptions = { ...options };
821
1270
  const cleanedArgv = [];
822
1271
  for (let i = 0; i < argv.length; i += 1) {
823
1272
  const token = String(argv[i] ?? "");
1273
+ if (
1274
+ token === "--api-key" ||
1275
+ token.startsWith("--api-key=") ||
1276
+ /^rg_(?:live|test)_/i.test(token) ||
1277
+ /(?:^|=)rg_(?:live|test)_/i.test(token)
1278
+ ) {
1279
+ const errOut = {
1280
+ ok: false,
1281
+ exitCode: 1,
1282
+ errorCode: "RAW_API_KEY_IN_ARGV_FORBIDDEN",
1283
+ message:
1284
+ "Pass REVCLAMP_API_KEY via environment variable, never as a CLI argument.",
1285
+ };
1286
+ console.error(JSON.stringify(errOut));
1287
+ return 1;
1288
+ }
824
1289
  if (token === "--secret" || token.startsWith("--secret=")) {
825
1290
  console.error(
826
1291
  "Error: passing webhook secrets via CLI flags (--secret) is forbidden; set REVCLAMP_WEBHOOK_SECRET in the environment."
@@ -1346,6 +1811,83 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
1346
1811
  return !res || res.error || res.result?.isError === true ? 1 : 0;
1347
1812
  }
1348
1813
 
1814
+ const initWorkspaceIdx = cleanedArgv.indexOf("--init-workspace");
1815
+ if (initWorkspaceIdx !== -1) {
1816
+ const rawEmailToken = String(cleanedArgv[initWorkspaceIdx + 1] ?? "").trim();
1817
+ const emailArg = rawEmailToken.startsWith("-") ? "" : rawEmailToken;
1818
+ const out = await runMcpCliOnboardingCommand({
1819
+ mode: "init-workspace",
1820
+ email: emailArg,
1821
+ argv,
1822
+ env: effectiveOptions.env || process.env,
1823
+ baseUrl:
1824
+ effectiveOptions.baseUrl ||
1825
+ effectiveOptions.endpointUrl ||
1826
+ effectiveOptions.apiUrl ||
1827
+ "",
1828
+ fetchImpl: effectiveOptions.fetchImpl,
1829
+ });
1830
+ if (out.ok) {
1831
+ console.log(JSON.stringify(out, null, 2));
1832
+ return 0;
1833
+ }
1834
+ console.error(JSON.stringify(out, null, 2));
1835
+ return 1;
1836
+ }
1837
+
1838
+ const resendOtpIdx = cleanedArgv.indexOf("--resend-otp");
1839
+ if (resendOtpIdx !== -1) {
1840
+ const rawEmailToken = String(cleanedArgv[resendOtpIdx + 1] ?? "").trim();
1841
+ const emailArg = rawEmailToken.startsWith("-") ? "" : rawEmailToken;
1842
+ const out = await runMcpCliOnboardingCommand({
1843
+ mode: "resend-otp",
1844
+ email: emailArg,
1845
+ argv,
1846
+ env: effectiveOptions.env || process.env,
1847
+ baseUrl:
1848
+ effectiveOptions.baseUrl ||
1849
+ effectiveOptions.endpointUrl ||
1850
+ effectiveOptions.apiUrl ||
1851
+ "",
1852
+ fetchImpl: effectiveOptions.fetchImpl,
1853
+ });
1854
+ if (out.ok) {
1855
+ console.log(JSON.stringify(out, null, 2));
1856
+ return 0;
1857
+ }
1858
+ console.error(JSON.stringify(out, null, 2));
1859
+ return 1;
1860
+ }
1861
+
1862
+ const verifyOtpIdx = cleanedArgv.indexOf("--verify-otp");
1863
+ if (verifyOtpIdx !== -1) {
1864
+ const rawEmailToken = String(cleanedArgv[verifyOtpIdx + 1] ?? "").trim();
1865
+ const emailArg = rawEmailToken.startsWith("-") ? "" : rawEmailToken;
1866
+ const rawCodeToken = emailArg
1867
+ ? String(cleanedArgv[verifyOtpIdx + 2] ?? "").trim()
1868
+ : "";
1869
+ const otpCodeArg = rawCodeToken.startsWith("-") ? "" : rawCodeToken;
1870
+ const out = await runMcpCliOnboardingCommand({
1871
+ mode: "verify-otp",
1872
+ email: emailArg,
1873
+ otpCode: otpCodeArg,
1874
+ argv,
1875
+ env: effectiveOptions.env || process.env,
1876
+ baseUrl:
1877
+ effectiveOptions.baseUrl ||
1878
+ effectiveOptions.endpointUrl ||
1879
+ effectiveOptions.apiUrl ||
1880
+ "",
1881
+ fetchImpl: effectiveOptions.fetchImpl,
1882
+ });
1883
+ if (out.ok) {
1884
+ console.log(JSON.stringify(out, null, 2));
1885
+ return 0;
1886
+ }
1887
+ console.error(JSON.stringify(out, null, 2));
1888
+ return 1;
1889
+ }
1890
+
1349
1891
  const callIdx = cleanedArgv.indexOf("--call");
1350
1892
  if (callIdx !== -1) {
1351
1893
  if (cleanedArgv.length > callIdx + 3) {
package/local_engine.mjs CHANGED
@@ -12,7 +12,7 @@
12
12
  * caller-supplied payloads. Never fabricates live consultation IDs or card-network
13
13
  * dispute evidence unless `demo: true` is explicitly passed (`sample: true`).
14
14
  * - Cloud Workspace Mode (`REVCLAMP_API_KEY` on https://api.revclamp.com/api/mcp):
15
- * Unlocks the 90-day SQLite WAL usage ledger, 24/7 autonomous Stripe dispute &
15
+ * Unlocks the 90-day durable transactional usage ledger, 24/7 autonomous Stripe dispute &
16
16
  * entitlement webhook outbox, and live European Commission VIES consultation IDs.
17
17
  */
18
18
 
@@ -145,7 +145,7 @@ export const LOCAL_MCP_RESOURCES = Object.freeze([
145
145
  uri: "revclamp://docs/open-core-capabilities",
146
146
  name: "RevClamp Open-Core vs Hosted Cloud Capabilities",
147
147
  description:
148
- "Reference summary of the 9 local deterministic MCP tools vs. hosted REVCLAMP_API_KEY capabilities (90-day SQLite WAL ledger, 24/7 Stripe webhook outbox, and live EU VIES consultation IDs).",
148
+ "Reference summary of the 9 local deterministic MCP tools vs. hosted REVCLAMP_API_KEY capabilities (90-day durable transactional ledger, 24/7 Stripe webhook outbox, and live EU VIES consultation IDs).",
149
149
  mimeType: "application/json",
150
150
  }),
151
151
  ]);
@@ -189,7 +189,7 @@ const EU_VIES_FORMAT_RULES = Object.freeze({
189
189
  DK: /^DK\d{8}$/,
190
190
  EE: /^EE\d{9}$/,
191
191
  EL: /^EL\d{9}$/,
192
- ES: /^ES[A-Z0-9]\d{7}[A-Z0-9]$/,
192
+ ES: /^ES(?:[A-Z]\d{7}[A-Z0-9]|[0-9]\d{7}[A-Z])$/,
193
193
  FI: /^FI\d{8}$/,
194
194
  FR: /^FR[A-HJ-NP-Z0-9]{2}\d{9}$/,
195
195
  HR: /^HR\d{11}$/,
@@ -214,6 +214,7 @@ const REVCLAMP_START_MARKER = "--- BEGIN REVCLAMP CE3 EVIDENCE ---";
214
214
  const REVCLAMP_END_MARKER = "--- END REVCLAMP CE3 EVIDENCE ---";
215
215
  const REVCLAMP_BRACKET_START_MARKER = "[RevClamp Verified Product Usage Log]";
216
216
  const REVCLAMP_BRACKET_END_MARKER = "[/RevClamp Verified Product Usage Log]";
217
+ const REVCLAMP_BRACKET_ALT_END_MARKER = "[End RevClamp Log]";
217
218
  const PRODUCTION_EMPTY_BLOCK_WRAPPER_CHARS =
218
219
  REVCLAMP_BRACKET_START_MARKER.length + 1 + REVCLAMP_BRACKET_END_MARKER.length;
219
220
  const MAX_ACCESS_ACTIVITY_LOG_CHARS = 18_000;
@@ -654,14 +655,20 @@ export function validateArgsAgainstSchema(schema, args) {
654
655
  function normalizeCountryCode(raw) {
655
656
  const clean = String(raw || "")
656
657
  .trim()
657
- .toUpperCase();
658
- return clean === "GR" ? "EL" : clean;
658
+ .toUpperCase()
659
+ .replace(/[\s.\-/:_]+/g, "");
660
+ if (clean === "GR") return "EL";
661
+ if (clean === "GBNIR") return "XI";
662
+ return clean;
659
663
  }
660
664
 
661
665
  function normalizeVatId(rawVat) {
662
- const compact = String(rawVat || "")
666
+ let compact = String(rawVat || "")
663
667
  .toUpperCase()
664
- .replace(/[\s.\-/:]+/g, "");
668
+ .replace(/[\s.\-/:_]+/g, "");
669
+ if (compact.startsWith("GBNIR")) {
670
+ compact = `XI${compact.slice(5)}`;
671
+ }
665
672
  if (compact.length < 4) {
666
673
  return { validFormat: false, memberState: "", vatNumber: "", fullVatId: compact };
667
674
  }
@@ -693,50 +700,107 @@ function sanitizeLogToken(raw, maxLen = 96) {
693
700
 
694
701
  function stripExistingRevClampBlock(rawNotes) {
695
702
  if (typeof rawNotes !== "string" || !rawNotes) {
696
- return { ok: true, hadBlock: false, prefix: "", suffix: "", text: "" };
703
+ return {
704
+ ok: true,
705
+ hadBlock: false,
706
+ prefix: "",
707
+ suffix: "",
708
+ text: "",
709
+ blockUtf16CodeUnits: 0,
710
+ };
697
711
  }
698
712
  const hasCanonical =
699
713
  rawNotes.includes(REVCLAMP_START_MARKER) || rawNotes.includes(REVCLAMP_END_MARKER);
714
+ const hasBracketPrimaryEnd = rawNotes.includes(REVCLAMP_BRACKET_END_MARKER);
715
+ const hasBracketAltEnd = rawNotes.includes(REVCLAMP_BRACKET_ALT_END_MARKER);
700
716
  const hasBracket =
701
- rawNotes.includes(REVCLAMP_BRACKET_START_MARKER) ||
702
- rawNotes.includes(REVCLAMP_BRACKET_END_MARKER);
717
+ rawNotes.includes(REVCLAMP_BRACKET_START_MARKER) || hasBracketPrimaryEnd || hasBracketAltEnd;
703
718
  if (!hasCanonical && !hasBracket) {
704
- return { ok: true, hadBlock: false, prefix: rawNotes, suffix: "", text: rawNotes.trim() };
719
+ return {
720
+ ok: true,
721
+ hadBlock: false,
722
+ prefix: rawNotes,
723
+ suffix: "",
724
+ text: rawNotes.trim(),
725
+ blockUtf16CodeUnits: 0,
726
+ };
705
727
  }
706
- if (hasCanonical && hasBracket) {
707
- return { ok: false, hadBlock: true, prefix: rawNotes, suffix: "", text: rawNotes.trim() };
728
+ if ((hasCanonical && hasBracket) || (hasBracketPrimaryEnd && hasBracketAltEnd)) {
729
+ return {
730
+ ok: false,
731
+ hadBlock: true,
732
+ prefix: rawNotes,
733
+ suffix: "",
734
+ text: rawNotes.trim(),
735
+ blockUtf16CodeUnits: 0,
736
+ };
708
737
  }
709
738
  const activeStart = hasCanonical ? REVCLAMP_START_MARKER : REVCLAMP_BRACKET_START_MARKER;
710
- const activeEnd = hasCanonical ? REVCLAMP_END_MARKER : REVCLAMP_BRACKET_END_MARKER;
739
+ const activeEnd = hasCanonical
740
+ ? REVCLAMP_END_MARKER
741
+ : hasBracketAltEnd
742
+ ? REVCLAMP_BRACKET_ALT_END_MARKER
743
+ : REVCLAMP_BRACKET_END_MARKER;
711
744
  let cursor = 0;
712
745
  const prefixSegments = [];
713
746
  let trailingSegment = "";
747
+ let blockUtf16CodeUnits = 0;
714
748
  while (cursor < rawNotes.length) {
715
749
  const startIdx = rawNotes.indexOf(activeStart, cursor);
716
750
  if (startIdx === -1) {
717
751
  if (rawNotes.indexOf(activeEnd, cursor) !== -1) {
718
- return { ok: false, hadBlock: true, prefix: rawNotes, suffix: "", text: rawNotes.trim() };
752
+ return {
753
+ ok: false,
754
+ hadBlock: true,
755
+ prefix: rawNotes,
756
+ suffix: "",
757
+ text: rawNotes.trim(),
758
+ blockUtf16CodeUnits: 0,
759
+ };
719
760
  }
720
761
  trailingSegment = rawNotes.slice(cursor);
721
762
  break;
722
763
  }
723
764
  if (rawNotes.slice(cursor, startIdx).includes(activeEnd)) {
724
- return { ok: false, hadBlock: true, prefix: rawNotes, suffix: "", text: rawNotes.trim() };
765
+ return {
766
+ ok: false,
767
+ hadBlock: true,
768
+ prefix: rawNotes,
769
+ suffix: "",
770
+ text: rawNotes.trim(),
771
+ blockUtf16CodeUnits: 0,
772
+ };
725
773
  }
726
774
  const contentStart = startIdx + activeStart.length;
727
775
  const endIdx = rawNotes.indexOf(activeEnd, contentStart);
728
776
  if (endIdx === -1) {
729
- return { ok: false, hadBlock: true, prefix: rawNotes, suffix: "", text: rawNotes.trim() };
777
+ return {
778
+ ok: false,
779
+ hadBlock: true,
780
+ prefix: rawNotes,
781
+ suffix: "",
782
+ text: rawNotes.trim(),
783
+ blockUtf16CodeUnits: 0,
784
+ };
730
785
  }
731
786
  const nextStartIdx = rawNotes.indexOf(activeStart, contentStart);
732
787
  if (nextStartIdx !== -1 && nextStartIdx < endIdx) {
733
- return { ok: false, hadBlock: true, prefix: rawNotes, suffix: "", text: rawNotes.trim() };
788
+ return {
789
+ ok: false,
790
+ hadBlock: true,
791
+ prefix: rawNotes,
792
+ suffix: "",
793
+ text: rawNotes.trim(),
794
+ blockUtf16CodeUnits: 0,
795
+ };
734
796
  }
735
797
  const beforeSegment = rawNotes.slice(cursor, startIdx).replace(/\n{1,2}$/, "");
736
798
  if (beforeSegment.length > 0) {
737
799
  prefixSegments.push(beforeSegment);
738
800
  }
739
- cursor = endIdx + activeEnd.length;
801
+ const blockEndCursor = endIdx + activeEnd.length;
802
+ blockUtf16CodeUnits += blockEndCursor - startIdx;
803
+ cursor = blockEndCursor;
740
804
  if (rawNotes.slice(cursor, cursor + 2) === "\n\n") {
741
805
  cursor += 2;
742
806
  } else if (rawNotes[cursor] === "\n") {
@@ -752,6 +816,7 @@ function stripExistingRevClampBlock(rawNotes) {
752
816
  prefix,
753
817
  suffix,
754
818
  text: keptTrimmed.join("\n\n").trim(),
819
+ blockUtf16CodeUnits,
755
820
  };
756
821
  }
757
822
 
@@ -949,14 +1014,37 @@ function evaluateLocalGhostUsage(subscriptionsEnv, usageEvent, dunningGracePerio
949
1014
  reason: "UNMATCHED_CUSTOMER_ID",
950
1015
  };
951
1016
  }
1017
+ const hasPositiveCustomerIdMatch = Boolean(evCustomerId && subCus && subCus === evCustomerId);
1018
+ const hasPositiveEmailMatch = Boolean(evEmail && subEmail && subEmail === evEmail);
1019
+ if (!hasPositiveCustomerIdMatch && !hasPositiveEmailMatch) {
1020
+ return {
1021
+ ok: true,
1022
+ executionMode: "local_open_source_engine",
1023
+ isGhostUsage: false,
1024
+ decision: "HOLD_CUSTOMER_MISMATCH",
1025
+ reason: subCus ? "UNMATCHED_CUSTOMER_ID" : "UNMATCHED_CUSTOMER_EMAIL",
1026
+ };
1027
+ }
952
1028
  const st = sub.status.trim();
953
1029
  if (ENTITLED_SUB_STATUSES.has(st)) {
1030
+ const collectionPaused = Boolean(
1031
+ sub.pause_collection && typeof sub.pause_collection === "object"
1032
+ );
954
1033
  return {
955
1034
  ok: true,
956
1035
  executionMode: "local_open_source_engine",
957
1036
  isGhostUsage: false,
958
1037
  decision: "ALLOW_ENTITLED",
959
1038
  reason: "ACTIVE_SUBSCRIPTION_PRESENT",
1039
+ ...(collectionPaused
1040
+ ? {
1041
+ collectionPaused: true,
1042
+ pauseCollectionBehavior:
1043
+ typeof sub.pause_collection.behavior === "string"
1044
+ ? sub.pause_collection.behavior
1045
+ : "void",
1046
+ }
1047
+ : {}),
960
1048
  };
961
1049
  }
962
1050
  if (sub.items?.has_more === true) {
@@ -1093,7 +1181,36 @@ function parseStrictIpv4Octets(str) {
1093
1181
 
1094
1182
  function normalizeIpForCe3Correlation(ip) {
1095
1183
  if (typeof ip !== "string") return null;
1096
- const clean = ip.trim().replace(/^\[|\]$/g, "").replace(/%[^\s%\]]+$/, "").trim().toLowerCase();
1184
+ let raw = ip.trim().toLowerCase();
1185
+ if (!raw || raw.includes(",")) return null;
1186
+ if (raw.startsWith("ipv4:")) {
1187
+ raw = raw.slice(5).trim();
1188
+ } else if (raw.startsWith("ipv6_64:")) {
1189
+ const v6Match = raw.match(
1190
+ /^ipv6_64:([0-9a-f]{1,4}):([0-9a-f]{1,4}):([0-9a-f]{1,4}):([0-9a-f]{1,4})$/
1191
+ );
1192
+ if (!v6Match) return null;
1193
+ return `ipv6_64:${v6Match
1194
+ .slice(1, 5)
1195
+ .map((w) => Number.parseInt(w, 16).toString(16).padStart(4, "0"))
1196
+ .join(":")}`;
1197
+ }
1198
+ const bracketPortMatch = raw.match(/^\[([^\]]+)\](?::(\d{1,5}))?$/);
1199
+ if (bracketPortMatch) {
1200
+ if (bracketPortMatch[2] !== undefined) {
1201
+ const portNum = Number(bracketPortMatch[2]);
1202
+ if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) return null;
1203
+ }
1204
+ raw = bracketPortMatch[1];
1205
+ } else {
1206
+ const v4PortMatch = raw.match(/^(\d{1,3}(?:\.\d{1,3}){3}):(\d{1,5})$/);
1207
+ if (v4PortMatch) {
1208
+ const portNum = Number(v4PortMatch[2]);
1209
+ if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) return null;
1210
+ raw = v4PortMatch[1];
1211
+ }
1212
+ }
1213
+ const clean = raw.replace(/^\[|\]$/g, "").replace(/%[^\s%\]]+$/, "").trim();
1097
1214
  if (!clean) return null;
1098
1215
  const directV4 = parseStrictIpv4Octets(clean);
1099
1216
  if (directV4) return `ipv4:${directV4.join(".")}`;
@@ -1224,14 +1341,30 @@ function extractNormalizedCe3Identifiers(tx) {
1224
1341
 
1225
1342
  function extractPaymentCredential(tx) {
1226
1343
  if (!tx || typeof tx !== "object") return "";
1227
- const raw =
1228
- tx.card_fingerprint ||
1229
- tx.payment_method_fingerprint ||
1230
- tx.payment_method ||
1231
- tx.payment_method_details?.card?.fingerprint ||
1232
- tx.card?.fingerprint ||
1233
- "";
1234
- return sanitizeLogToken(raw, 96);
1344
+ const fpCandidates = [
1345
+ tx.card_fingerprint,
1346
+ tx.payment_method_fingerprint,
1347
+ tx.payment_method_details?.card?.fingerprint,
1348
+ tx.payment_method && typeof tx.payment_method === "object"
1349
+ ? tx.payment_method.card?.fingerprint
1350
+ : "",
1351
+ tx.card?.fingerprint,
1352
+ tx.source?.card?.fingerprint,
1353
+ ];
1354
+ for (const cand of fpCandidates) {
1355
+ if (typeof cand === "string" && cand.trim()) {
1356
+ return sanitizeLogToken(cand.trim(), 96);
1357
+ }
1358
+ }
1359
+ const pmId =
1360
+ typeof tx.payment_method === "string"
1361
+ ? tx.payment_method
1362
+ : tx.payment_method &&
1363
+ typeof tx.payment_method === "object" &&
1364
+ typeof tx.payment_method.id === "string"
1365
+ ? tx.payment_method.id
1366
+ : "";
1367
+ return sanitizeLogToken(pmId, 96);
1235
1368
  }
1236
1369
 
1237
1370
  function evaluateLocalVisaCe3Qualification(disputeReason, disputedTransaction, priorTransactions) {
@@ -1372,7 +1505,34 @@ function evaluateLocalVisaCe3Qualification(disputeReason, disputedTransaction, p
1372
1505
  };
1373
1506
  }
1374
1507
 
1375
- export function executeLocalMcpTool(toolName, rawArgs = {}) {
1508
+ export const LOCAL_STATELESS_CLOUD_UPGRADE_HINT = Object.freeze({
1509
+ mode: "LOCAL_STATELESS_OPEN_CORE",
1510
+ message:
1511
+ "Running in stateless local mode. Provision a free or paid Cloud Workspace for 90-day event persistence, automated webhook revocation, and Slack/Email alerts.",
1512
+ command: "npx --yes revclamp-mcp --init-workspace <work_email>",
1513
+ cliActivateCommand: "npx -y revclamp-mcp@1.20.11 --init-workspace you@company.com",
1514
+ upgradeUrl: "https://revclamp.com/pricing?source=mcp_cli",
1515
+ });
1516
+
1517
+ export function executeLocalMcpTool(toolName, rawArgs = {}, options = {}) {
1518
+ const result = executeLocalMcpToolCore(toolName, rawArgs);
1519
+ const suppressHint = Boolean(options?.suppressCloudUpgradeHint || options?.apiKey);
1520
+ if (
1521
+ !suppressHint &&
1522
+ result &&
1523
+ typeof result === "object" &&
1524
+ result.ok !== false &&
1525
+ !result.error
1526
+ ) {
1527
+ return {
1528
+ ...result,
1529
+ cloudUpgradeHint: LOCAL_STATELESS_CLOUD_UPGRADE_HINT,
1530
+ };
1531
+ }
1532
+ return result;
1533
+ }
1534
+
1535
+ function executeLocalMcpToolCore(toolName, rawArgs = {}) {
1376
1536
  const inputArgs =
1377
1537
  rawArgs === undefined
1378
1538
  ? {}
@@ -1988,11 +2148,23 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
1988
2148
  const malformedExistingMarkers = Boolean(!strippedNotes.ok);
1989
2149
  const warnings = malformedExistingMarkers ? ["MALFORMED_EXISTING_MARKERS"] : [];
1990
2150
  const trimmedExistingNotes = existingMerchantNotes.trim();
2151
+ const normalizedTrimmedExisting = trimmedExistingNotes.replace(/\r?\n+/g, "\n");
1991
2152
  const outsideMarkerText = `${strippedNotes.prefix || ""}\n${strippedNotes.suffix || ""}`;
2153
+ const outsideJoinedSingle = [strippedNotes.prefix || "", strippedNotes.suffix || ""]
2154
+ .filter(Boolean)
2155
+ .join("\n");
2156
+ const outsideJoinedDouble = [strippedNotes.prefix || "", strippedNotes.suffix || ""]
2157
+ .filter(Boolean)
2158
+ .join("\n\n");
2159
+ const outsideNormalizedNewlines = outsideJoinedSingle.replace(/\r?\n+/g, "\n");
1992
2160
  const preservesMerchantNotes = Boolean(
1993
2161
  nonDestructiveMarkersPresent &&
1994
2162
  strippedNotes.text.length > 0 &&
1995
- (trimmedExistingNotes === "" || outsideMarkerText.includes(trimmedExistingNotes))
2163
+ (trimmedExistingNotes === "" ||
2164
+ outsideMarkerText.includes(trimmedExistingNotes) ||
2165
+ outsideJoinedSingle.includes(trimmedExistingNotes) ||
2166
+ outsideJoinedDouble.includes(trimmedExistingNotes) ||
2167
+ outsideNormalizedNewlines.includes(normalizedTrimmedExisting))
1996
2168
  );
1997
2169
 
1998
2170
  const extractedNotesLen =
@@ -2004,12 +2176,10 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2004
2176
  const preSeparatorUnits = capacityPrefix.length > 0 ? 2 : 0;
2005
2177
  const postSeparatorUnits = capacitySuffix.length > 0 ? 2 : 0;
2006
2178
  const merchantNotesUtf16CodeUnits = capacityPrefix.length + capacitySuffix.length;
2179
+ const wrapperOverheadUtf16CodeUnits =
2180
+ preSeparatorUnits + PRODUCTION_EMPTY_BLOCK_WRAPPER_CHARS + postSeparatorUnits;
2007
2181
  const fixedWrapperUtf16CodeUnits =
2008
- capacityPrefix.length +
2009
- preSeparatorUnits +
2010
- PRODUCTION_EMPTY_BLOCK_WRAPPER_CHARS +
2011
- postSeparatorUnits +
2012
- capacitySuffix.length;
2182
+ capacityPrefix.length + wrapperOverheadUtf16CodeUnits + capacitySuffix.length;
2013
2183
  const merchantLogAtCapacity = Boolean(
2014
2184
  fixedWrapperUtf16CodeUnits >= MAX_ACCESS_ACTIVITY_LOG_CHARS
2015
2185
  );
@@ -2027,6 +2197,20 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2027
2197
  const within18kCeiling = utf16CodeUnits <= MAX_ACCESS_ACTIVITY_LOG_CHARS;
2028
2198
  const within20kHardCeiling = utf16CodeUnits <= 20_000;
2029
2199
  const exceeds20kHardCeiling = utf16CodeUnits > 20_000;
2200
+ const segmentBreakdown = {
2201
+ prefixUtf16CodeUnits: capacityPrefix.length,
2202
+ preSeparatorUtf16CodeUnits: preSeparatorUnits,
2203
+ revClampBlockUtf16CodeUnits: strippedNotes.blockUtf16CodeUnits || 0,
2204
+ postSeparatorUtf16CodeUnits: postSeparatorUnits,
2205
+ suffixUtf16CodeUnits: capacitySuffix.length,
2206
+ wrapperOverheadUtf16CodeUnits,
2207
+ totalMergedUtf16CodeUnits: utf16CodeUnits,
2208
+ remainingSafeBudgetUtf16CodeUnits: remainingUtf16Units,
2209
+ overBudgetByUtf16CodeUnits: Math.max(
2210
+ 0,
2211
+ utf16CodeUnits - MAX_ACCESS_ACTIVITY_LOG_CHARS
2212
+ ),
2213
+ };
2030
2214
  let overallVerdict = "PASS_CE3_READY";
2031
2215
  if (!within18kCeiling) {
2032
2216
  overallVerdict = "FAIL_UTF16_OVERFLOW";
@@ -2074,6 +2258,7 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2074
2258
  merchantNotesPreserved: preservesMerchantNotes,
2075
2259
  merchantNotesUtf16CodeUnits,
2076
2260
  fixedWrapperUtf16CodeUnits,
2261
+ segmentBreakdown,
2077
2262
  merchantLogAtCapacity,
2078
2263
  stagingActionWhenAtCapacity,
2079
2264
  priorMatchedCount,
@@ -2416,7 +2601,18 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2416
2601
  };
2417
2602
  }
2418
2603
 
2419
- if (viesMode === "MS_UNAVAILABLE" || viesMode === "GLOBAL_MAX_CONCURRENT_REQ") {
2604
+ const TRANSIENT_SIM_MODES = new Set([
2605
+ "MS_UNAVAILABLE",
2606
+ "GLOBAL_MAX_CONCURRENT_REQ",
2607
+ "MS_MAX_CONCURRENT_REQ",
2608
+ "MS_MAX_CONCURRENT_REQ_TIME",
2609
+ "GLOBAL_MAX_CONCURRENT_REQ_TIME",
2610
+ "SERVICE_UNAVAILABLE",
2611
+ "TIMEOUT",
2612
+ "SERVER_BUSY",
2613
+ "NETWORK_ERROR",
2614
+ ]);
2615
+ if (TRANSIENT_SIM_MODES.has(viesMode)) {
2420
2616
  return {
2421
2617
  ok: true,
2422
2618
  executionMode: "local_open_source_engine",
@@ -2434,7 +2630,25 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2434
2630
  };
2435
2631
  }
2436
2632
 
2437
- if (viesMode === "INVALID") {
2633
+ if (viesMode === "INVALID_REQUESTER_INFO" || viesMode === "IP_BLOCKED") {
2634
+ return {
2635
+ ok: true,
2636
+ executionMode: "local_open_source_engine",
2637
+ sample: true,
2638
+ valid: null,
2639
+ formatValid: true,
2640
+ status: "HOLD_UNVERIFIED_VIES_FAULT",
2641
+ reason: `VIES_GATEWAY_FAULT_${viesMode}`,
2642
+ sellerCountry,
2643
+ normalizedVatId: norm.fullVatId,
2644
+ memberState: norm.memberState,
2645
+ reverseChargeEligible: false,
2646
+ zeroRateAllowed: false,
2647
+ consultationId: null,
2648
+ };
2649
+ }
2650
+
2651
+ if (viesMode === "INVALID" || viesMode === "VAT_BLOCKED" || viesMode === "INVALID_INPUT") {
2438
2652
  return {
2439
2653
  ok: true,
2440
2654
  executionMode: "local_open_source_engine",
@@ -2442,7 +2656,25 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2442
2656
  valid: false,
2443
2657
  formatValid: true,
2444
2658
  status: "HOLD_INVALID_VAT",
2445
- reason: "VIES_DEFINITE_INVALID",
2659
+ reason: viesMode === "INVALID" ? "VIES_DEFINITE_INVALID" : `VIES_${viesMode}`,
2660
+ sellerCountry,
2661
+ normalizedVatId: norm.fullVatId,
2662
+ memberState: norm.memberState,
2663
+ reverseChargeEligible: false,
2664
+ zeroRateAllowed: false,
2665
+ consultationId: null,
2666
+ };
2667
+ }
2668
+
2669
+ if (viesMode !== "VALID" && viesMode !== "VERIFIED") {
2670
+ return {
2671
+ ok: true,
2672
+ executionMode: "local_open_source_engine",
2673
+ sample: true,
2674
+ valid: null,
2675
+ formatValid: true,
2676
+ status: "HOLD_UNVERIFIED_VIES_FAULT",
2677
+ reason: "UNRECOGNIZED_VIES_SIMULATION_MODE",
2446
2678
  sellerCountry,
2447
2679
  normalizedVatId: norm.fullVatId,
2448
2680
  memberState: norm.memberState,
@@ -2559,20 +2791,21 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2559
2791
 
2560
2792
  const effectiveTotalCents = Math.max(0, orderTotalCents - creditNotesCents);
2561
2793
  const hasExplicitDepositCents = args.requiredDepositCents !== undefined;
2562
- const requiredDepositCents = hasExplicitDepositCents
2794
+ const rawRequiredDepositCents = hasExplicitDepositCents
2563
2795
  ? args.requiredDepositCents
2564
2796
  : Math.ceil((effectiveTotalCents * requiredDepositPercent) / 100);
2565
2797
  if (
2566
- !Number.isSafeInteger(requiredDepositCents) ||
2567
- requiredDepositCents < 0 ||
2568
- requiredDepositCents > effectiveTotalCents
2798
+ !Number.isSafeInteger(rawRequiredDepositCents) ||
2799
+ rawRequiredDepositCents < 0 ||
2800
+ rawRequiredDepositCents > orderTotalCents
2569
2801
  ) {
2570
2802
  const err = new Error(
2571
- "INVALID_REQUIRED_DEPOSIT_CENTS: requiredDepositCents must be an integer between 0 and effectiveTotalCents."
2803
+ "INVALID_REQUIRED_DEPOSIT_CENTS: requiredDepositCents must be an integer between 0 and totalOrderCents."
2572
2804
  );
2573
2805
  err.code = -32602;
2574
2806
  throw err;
2575
2807
  }
2808
+ const requiredDepositCents = Math.min(rawRequiredDepositCents, effectiveTotalCents);
2576
2809
 
2577
2810
  const shortfallRawCents = Math.max(0, effectiveTotalCents - paidAmountCents);
2578
2811
  const overpaidCents = Math.max(0, paidAmountCents - effectiveTotalCents);
@@ -2898,7 +3131,9 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2898
3131
  ? args.existingAccessActivityLog
2899
3132
  : typeof args.existingUncategorizedText === "string"
2900
3133
  ? args.existingUncategorizedText
2901
- : "";
3134
+ : typeof args.existingMerchantNotes === "string"
3135
+ ? args.existingMerchantNotes
3136
+ : "";
2902
3137
  const strippedNotes = stripExistingRevClampBlock(rawExistingLog);
2903
3138
  if (!strippedNotes.ok) {
2904
3139
  const rawUtf8Bytes = Buffer.byteLength(rawExistingLog, "utf8");
@@ -2919,25 +3154,40 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2919
3154
  },
2920
3155
  };
2921
3156
  }
2922
- const preservedMerchantNotes = strippedNotes.text;
3157
+ const merchantPrefixLog = strippedNotes.prefix || "";
3158
+ const merchantSuffixLog = strippedNotes.suffix || "";
3159
+ const totalMerchantChars = merchantPrefixLog.length + merchantSuffixLog.length;
3160
+ const preSeparator = merchantPrefixLog.length > 0 ? "\n\n" : "";
3161
+ const postSeparator = merchantSuffixLog.length > 0 ? "\n\n" : "";
2923
3162
  const headerLine = isDemo
2924
3163
  ? `[DEMO SAMPLE - NOT FOR DISPUTE SUBMISSION | dispute=${cleanDisputeId} | customer=${cleanCustomerId} | submit=false]`
2925
3164
  : `[RevClamp Verified Product Usage Log | dispute=${cleanDisputeId} | customer=${cleanCustomerId} | submit=false]`;
2926
3165
 
2927
- const prefix = preservedMerchantNotes ? `${preservedMerchantNotes}\n\n` : "";
3166
+ const wrapperOverheadUtf16CodeUnits =
3167
+ preSeparator.length +
3168
+ REVCLAMP_START_MARKER.length +
3169
+ 1 +
3170
+ REVCLAMP_END_MARKER.length +
3171
+ postSeparator.length;
3172
+ const fixedWrapperChars =
3173
+ merchantPrefixLog.length + wrapperOverheadUtf16CodeUnits + merchantSuffixLog.length;
2928
3174
  const fixedEnvelopeChars =
2929
- prefix.length +
3175
+ merchantPrefixLog.length +
3176
+ preSeparator.length +
2930
3177
  REVCLAMP_START_MARKER.length +
2931
3178
  1 +
2932
3179
  headerLine.length +
2933
3180
  1 +
2934
- REVCLAMP_END_MARKER.length;
3181
+ REVCLAMP_END_MARKER.length +
3182
+ postSeparator.length +
3183
+ merchantSuffixLog.length;
2935
3184
  const fixedEnvelopeUtf8Bytes = Buffer.byteLength(
2936
- `${prefix}${REVCLAMP_START_MARKER}\n${headerLine}\n${REVCLAMP_END_MARKER}`,
3185
+ `${merchantPrefixLog}${preSeparator}${REVCLAMP_START_MARKER}\n${headerLine}\n${REVCLAMP_END_MARKER}${postSeparator}${merchantSuffixLog}`,
2937
3186
  "utf8"
2938
3187
  );
2939
3188
 
2940
3189
  if (
3190
+ fixedWrapperChars >= MAX_ACCESS_ACTIVITY_LOG_CHARS ||
2941
3191
  fixedEnvelopeChars > MAX_ACCESS_ACTIVITY_LOG_CHARS ||
2942
3192
  fixedEnvelopeUtf8Bytes > MAX_LOG_UTF8_BYTES
2943
3193
  ) {
@@ -2949,6 +3199,23 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2949
3199
  skipped: true,
2950
3200
  reason: "EXISTING_MERCHANT_NOTES_EXCEED_18K_CEILING",
2951
3201
  submit: false,
3202
+ segmentBreakdown: {
3203
+ prefixUtf16CodeUnits: merchantPrefixLog.length,
3204
+ preSeparatorUtf16CodeUnits: preSeparator.length,
3205
+ revClampBlockUtf16CodeUnits: 0,
3206
+ postSeparatorUtf16CodeUnits: postSeparator.length,
3207
+ suffixUtf16CodeUnits: merchantSuffixLog.length,
3208
+ wrapperOverheadUtf16CodeUnits,
3209
+ totalMergedUtf16CodeUnits: totalMerchantChars,
3210
+ remainingSafeBudgetUtf16CodeUnits: Math.max(
3211
+ 0,
3212
+ MAX_ACCESS_ACTIVITY_LOG_CHARS - totalMerchantChars
3213
+ ),
3214
+ overBudgetByUtf16CodeUnits: Math.max(
3215
+ 0,
3216
+ fixedEnvelopeChars - MAX_ACCESS_ACTIVITY_LOG_CHARS
3217
+ ),
3218
+ },
2952
3219
  evidence: {},
2953
3220
  };
2954
3221
  }
@@ -2969,6 +3236,36 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2969
3236
  currentUtf8Bytes += lineUtf8Bytes;
2970
3237
  }
2971
3238
 
3239
+ if (packedLines.length === 0 && formattedLines.length > 0) {
3240
+ return {
3241
+ ok: true,
3242
+ executionMode: "local_open_source_engine",
3243
+ disputeId: cleanDisputeId,
3244
+ customerId: cleanCustomerId,
3245
+ skipped: true,
3246
+ reason: "EXISTING_MERCHANT_NOTES_EXCEED_18K_CEILING",
3247
+ submit: false,
3248
+ segmentBreakdown: {
3249
+ prefixUtf16CodeUnits: merchantPrefixLog.length,
3250
+ preSeparatorUtf16CodeUnits: preSeparator.length,
3251
+ revClampBlockUtf16CodeUnits: 0,
3252
+ postSeparatorUtf16CodeUnits: postSeparator.length,
3253
+ suffixUtf16CodeUnits: merchantSuffixLog.length,
3254
+ wrapperOverheadUtf16CodeUnits,
3255
+ totalMergedUtf16CodeUnits: totalMerchantChars,
3256
+ remainingSafeBudgetUtf16CodeUnits: Math.max(
3257
+ 0,
3258
+ MAX_ACCESS_ACTIVITY_LOG_CHARS - totalMerchantChars
3259
+ ),
3260
+ overBudgetByUtf16CodeUnits: Math.max(
3261
+ 0,
3262
+ fixedEnvelopeChars + 1 + formattedLines[0].length - MAX_ACCESS_ACTIVITY_LOG_CHARS
3263
+ ),
3264
+ },
3265
+ evidence: {},
3266
+ };
3267
+ }
3268
+
2972
3269
  const block = [
2973
3270
  REVCLAMP_START_MARKER,
2974
3271
  headerLine,
@@ -2976,7 +3273,7 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2976
3273
  REVCLAMP_END_MARKER,
2977
3274
  ].join("\n");
2978
3275
 
2979
- const merged = `${prefix}${block}`;
3276
+ const merged = `${merchantPrefixLog}${preSeparator}${block}${postSeparator}${merchantSuffixLog}`;
2980
3277
  const ce3Qualification = evaluateLocalVisaCe3Qualification(
2981
3278
  disputeReason,
2982
3279
  args.disputedTransaction,
@@ -2994,6 +3291,20 @@ export function executeLocalMcpTool(toolName, rawArgs = {}) {
2994
3291
  submit: false,
2995
3292
  utf16CodeUnits: merged.length,
2996
3293
  linesIncluded: packedLines.length,
3294
+ segmentBreakdown: {
3295
+ prefixUtf16CodeUnits: merchantPrefixLog.length,
3296
+ preSeparatorUtf16CodeUnits: preSeparator.length,
3297
+ revClampBlockUtf16CodeUnits: block.length,
3298
+ postSeparatorUtf16CodeUnits: postSeparator.length,
3299
+ suffixUtf16CodeUnits: merchantSuffixLog.length,
3300
+ wrapperOverheadUtf16CodeUnits,
3301
+ totalMergedUtf16CodeUnits: merged.length,
3302
+ remainingSafeBudgetUtf16CodeUnits: Math.max(
3303
+ 0,
3304
+ MAX_ACCESS_ACTIVITY_LOG_CHARS - merged.length
3305
+ ),
3306
+ overBudgetByUtf16CodeUnits: Math.max(0, merged.length - MAX_ACCESS_ACTIVITY_LOG_CHARS),
3307
+ },
2997
3308
  evidence: {
2998
3309
  access_activity_log: merged,
2999
3310
  },
@@ -3150,7 +3461,11 @@ function toSafeLocalRpcId(rawId) {
3150
3461
  return null;
3151
3462
  }
3152
3463
 
3153
- export function executeLocalMcpJsonRpc(rpcRequest, packageVersion = "1.20.9") {
3464
+ export function executeLocalMcpJsonRpc(
3465
+ rpcRequest,
3466
+ packageVersion = "1.20.11",
3467
+ options = {}
3468
+ ) {
3154
3469
  if (!rpcRequest || typeof rpcRequest !== "object" || Array.isArray(rpcRequest)) {
3155
3470
  return {
3156
3471
  jsonrpc: "2.0",
@@ -3264,7 +3579,7 @@ export function executeLocalMcpJsonRpc(rpcRequest, packageVersion = "1.20.9") {
3264
3579
  version: packageVersion,
3265
3580
  localExecution: "9 deterministic tools in local_engine.mjs (0 network calls by default)",
3266
3581
  cloudCapabilities: [
3267
- "90-day SQLite WAL usage & audit ledger",
3582
+ "90-day durable transactional usage & audit ledger",
3268
3583
  "24/7 autonomous Stripe dispute & entitlement webhook outbox",
3269
3584
  "Live EU Commission VIES SOAP consultation numbers (viesMode: 'LIVE')",
3270
3585
  ],
@@ -3434,7 +3749,7 @@ export function executeLocalMcpJsonRpc(rpcRequest, packageVersion = "1.20.9") {
3434
3749
  }
3435
3750
 
3436
3751
  try {
3437
- const structuredContent = executeLocalMcpTool(toolName, effectiveToolArgs);
3752
+ const structuredContent = executeLocalMcpTool(toolName, effectiveToolArgs, options);
3438
3753
  let responseObj = {
3439
3754
  jsonrpc: "2.0",
3440
3755
  id: safeId,
package/mcp-config.json CHANGED
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "manifest": {
28
28
  "name": "revclamp-mcp",
29
- "version": "1.20.9",
29
+ "version": "1.20.11",
30
30
  "tools": [
31
31
  "revclamp_audit_revenue_leakage",
32
32
  "revclamp_evaluate_ghost_usage",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "revclamp-mcp",
3
- "mcpName": "io.github.revclamp/revclamp-mcp",
4
- "version": "1.20.9",
3
+ "mcpName": "com.revclamp/revclamp-mcp",
4
+ "version": "1.20.11",
5
5
  "description": "Model Context Protocol (MCP) server and CLI for Stripe 2025-03-31.basil entitlement checks, Visa CE 3.0 dispute staging (submit: false), EU/XI VIES VAT validation, Shopify Plus B2B Net-15/30/60 reconciliation, webhook HMAC-SHA256 verification, 0% take-rate ROI calculation, 72-hour webhook replay & out-of-order state-machine simulation, and Stripe dispute evidence 18,000 UTF-16 code unit & CE 3.0 completeness linting.",
6
6
  "type": "module",
7
7
  "main": "./index.mjs",
@@ -46,6 +46,6 @@
46
46
  "node": ">=20.0.0"
47
47
  },
48
48
  "revclamp": {
49
- "engineSha256": "775f5ff489a939d0f262ef972da38598e11fef0ce9e1371e6980e3946209fbc5"
49
+ "engineSha256": "806f9291517805e75ce55636ad1e704e30c1b2a840bd1162c87ba7e92bd9a053"
50
50
  }
51
51
  }
package/server.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.revclamp/revclamp-mcp",
3
+ "name": "com.revclamp/revclamp-mcp",
4
4
  "title": "RevClamp MCP Server",
5
5
  "description": "Zero-take-rate Stripe 2025-03-31.basil, Visa CE 3.0 & EU/XI VIES VAT guardrails MCP server.",
6
6
  "repository": {
7
7
  "url": "https://github.com/lhphong09t2/revclamp-mcp",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.20.9",
10
+ "version": "1.20.11",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "revclamp-mcp",
16
- "version": "1.20.9",
16
+ "version": "1.20.11",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },