revclamp-mcp 1.20.7 → 1.20.8
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 +2 -2
- package/index.mjs +35 -2
- package/local_engine.mjs +147 -6
- package/mcp-config.json +1 -1
- package/package.json +5 -2
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Add `revclamp-mcp` to your `claude_desktop_config.json` or `.cursor/mcp.json`. B
|
|
|
41
41
|
"mcpServers": {
|
|
42
42
|
"revclamp": {
|
|
43
43
|
"command": "npx",
|
|
44
|
-
"args": ["-y", "github:
|
|
44
|
+
"args": ["-y", "github:lhphong09t2/revclamp-mcp"]
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -59,7 +59,7 @@ 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:
|
|
62
|
+
npx -y github:lhphong09t2/revclamp-mcp#v1.20.8 --self-test
|
|
63
63
|
|
|
64
64
|
# List all 9 MCP tools and JSON schemas (runs locally in-process)
|
|
65
65
|
node index.mjs --list-tools
|
package/index.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { fileURLToPath } from "node:url";
|
|
|
20
20
|
import {
|
|
21
21
|
LOCAL_MCP_TOOLS,
|
|
22
22
|
MCP_PROTOCOL_VERSION,
|
|
23
|
+
normalizeMcpToolArgs,
|
|
23
24
|
executeLocalMcpTool,
|
|
24
25
|
executeLocalMcpJsonRpc,
|
|
25
26
|
} from "./local_engine.mjs";
|
|
@@ -27,11 +28,12 @@ import {
|
|
|
27
28
|
export {
|
|
28
29
|
LOCAL_MCP_TOOLS,
|
|
29
30
|
MCP_PROTOCOL_VERSION,
|
|
31
|
+
normalizeMcpToolArgs,
|
|
30
32
|
executeLocalMcpTool,
|
|
31
33
|
executeLocalMcpJsonRpc,
|
|
32
34
|
};
|
|
33
35
|
|
|
34
|
-
export const PACKAGE_VERSION = "1.20.
|
|
36
|
+
export const PACKAGE_VERSION = "1.20.8";
|
|
35
37
|
export const DEFAULT_MCP_ENDPOINT = "https://api.revclamp.com/api/mcp";
|
|
36
38
|
export const ALLOWED_MCP_ORIGINS = Object.freeze(
|
|
37
39
|
new Set(["https://api.revclamp.com", "https://revclamp.com"])
|
|
@@ -41,7 +43,7 @@ export const MAX_STDIO_LINE_BYTES = MAX_PAYLOAD_BYTES;
|
|
|
41
43
|
export const REQUEST_TIMEOUT_MS = 10_000;
|
|
42
44
|
|
|
43
45
|
const LIVE_SECRET_GUARD_RE =
|
|
44
|
-
/(?:[sr]k_live_[A-Za-z0-9_-]{4,}|whsec_live_[A-Za-z0-9_-]{4,}|whsec_(?!test_|synth_|sandbox_)[A-Za-z0-9_-]{10,}|shpss_live_[A-Za-z0-9_-]{4,}|shpss_[0-9a-fA-F]{16,}|shp(?:at|ua|ca|pa)_[A-Za-z0-9_-]{8,}|lsq_[A-Za-z0-9_-]{4,}|r[gc]_live_[A-Za-z0-9_-]{4,}|AKIA[0-9A-Z]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----)/i;
|
|
46
|
+
/(?:[sr]k_live_[A-Za-z0-9_-]{4,}|whsec_live_[A-Za-z0-9_-]{4,}|whsec_(?!test_|synth_|sandbox_|demo_|sample_|mock_|example_|fake_|dummy_)[A-Za-z0-9_-]{10,}|shpss_live_[A-Za-z0-9_-]{4,}|shpss_[0-9a-fA-F]{16,}|shp(?:at|ua|ca|pa)_[A-Za-z0-9_-]{8,}|lsq_[A-Za-z0-9_-]{4,}|r[gc]_live_[A-Za-z0-9_-]{4,}|AKIA[0-9A-Z]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----)/i;
|
|
45
47
|
|
|
46
48
|
export function toSafeRpcId(rawId) {
|
|
47
49
|
if (typeof rawId === "number" && Number.isFinite(rawId)) {
|
|
@@ -314,6 +316,37 @@ export async function forwardMcpJsonRpc(
|
|
|
314
316
|
},
|
|
315
317
|
};
|
|
316
318
|
}
|
|
319
|
+
if (methodStr === "tools/call" && rpcRequest.params.arguments !== undefined) {
|
|
320
|
+
const normRes = normalizeMcpToolArgs(
|
|
321
|
+
String(rpcRequest.params?.name || "").trim(),
|
|
322
|
+
rpcRequest.params.arguments
|
|
323
|
+
);
|
|
324
|
+
if (!normRes.ok) {
|
|
325
|
+
if (isNotification) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
jsonrpc: "2.0",
|
|
330
|
+
id: safeId,
|
|
331
|
+
error: {
|
|
332
|
+
code: -32602,
|
|
333
|
+
message: normRes.message,
|
|
334
|
+
data: {
|
|
335
|
+
error: normRes.error,
|
|
336
|
+
reason: normRes.error,
|
|
337
|
+
...(normRes.canonicalKey ? { canonicalKey: normRes.canonicalKey } : {}),
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
rpcRequest = {
|
|
343
|
+
...rpcRequest,
|
|
344
|
+
params: {
|
|
345
|
+
...rpcRequest.params,
|
|
346
|
+
arguments: normRes.args,
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
317
350
|
}
|
|
318
351
|
|
|
319
352
|
try {
|
package/local_engine.mjs
CHANGED
|
@@ -26,7 +26,79 @@ export const SUPPORTED_MCP_PROTOCOL_VERSIONS = Object.freeze([
|
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
28
|
const LOCAL_LIVE_SECRET_GUARD_RE =
|
|
29
|
-
/(?:[sr]k_live_[A-Za-z0-9_-]{4,}|whsec_live_[A-Za-z0-9_-]{4,}|whsec_(?!test_|synth_|sandbox_)[A-Za-z0-9_-]{10,}|shpss_live_[A-Za-z0-9_-]{4,}|shpss_[0-9a-fA-F]{16,}|shp(?:at|ua|ca|pa)_[A-Za-z0-9_-]{8,}|lsq_[A-Za-z0-9_-]{4,}|r[gc]_live_[A-Za-z0-9_-]{4,}|AKIA[0-9A-Z]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----)/i;
|
|
29
|
+
/(?:[sr]k_live_[A-Za-z0-9_-]{4,}|whsec_live_[A-Za-z0-9_-]{4,}|whsec_(?!test_|synth_|sandbox_|demo_|sample_|mock_|example_|fake_|dummy_)[A-Za-z0-9_-]{10,}|shpss_live_[A-Za-z0-9_-]{4,}|shpss_[0-9a-fA-F]{16,}|shp(?:at|ua|ca|pa)_[A-Za-z0-9_-]{8,}|lsq_[A-Za-z0-9_-]{4,}|r[gc]_live_[A-Za-z0-9_-]{4,}|AKIA[0-9A-Z]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----)/i;
|
|
30
|
+
|
|
31
|
+
const LOCAL_MCP_TOOL_ARG_ALIAS_GROUPS = Object.freeze({
|
|
32
|
+
revclamp_verify_webhook_signature: Object.freeze([
|
|
33
|
+
Object.freeze({ canonical: "rawPayload", aliases: Object.freeze(["payload", "body"]) }),
|
|
34
|
+
Object.freeze({
|
|
35
|
+
canonical: "signatureHeader",
|
|
36
|
+
aliases: Object.freeze(["signature", "header", "stripeSignature", "shopifyHmac"]),
|
|
37
|
+
}),
|
|
38
|
+
Object.freeze({
|
|
39
|
+
canonical: "secret",
|
|
40
|
+
aliases: Object.freeze(["webhookSecret", "signingSecret"]),
|
|
41
|
+
}),
|
|
42
|
+
Object.freeze({ canonical: "timestampSec", aliases: Object.freeze(["timestamp"]) }),
|
|
43
|
+
Object.freeze({
|
|
44
|
+
canonical: "toleranceSec",
|
|
45
|
+
aliases: Object.freeze(["toleranceSeconds", "tolerance"]),
|
|
46
|
+
}),
|
|
47
|
+
]),
|
|
48
|
+
revclamp_simulate_webhook_replay: Object.freeze([
|
|
49
|
+
Object.freeze({ canonical: "scenarioPreset", aliases: Object.freeze(["preset"]) }),
|
|
50
|
+
Object.freeze({
|
|
51
|
+
canonical: "idempotencyWindowSec",
|
|
52
|
+
aliases: Object.freeze(["windowSec", "dedupWindowSec"]),
|
|
53
|
+
}),
|
|
54
|
+
]),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export function normalizeMcpToolArgs(toolName, rawArgs) {
|
|
58
|
+
if (rawArgs === null || typeof rawArgs !== "object" || Array.isArray(rawArgs)) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: "INVALID_ARGUMENTS_OBJECT",
|
|
62
|
+
message: "Invalid params: arguments must be a JSON object",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const normalized = { ...rawArgs };
|
|
66
|
+
const groups = Object.hasOwn(LOCAL_MCP_TOOL_ARG_ALIAS_GROUPS, toolName)
|
|
67
|
+
? LOCAL_MCP_TOOL_ARG_ALIAS_GROUPS[toolName]
|
|
68
|
+
: null;
|
|
69
|
+
if (!groups) {
|
|
70
|
+
return { ok: true, args: normalized };
|
|
71
|
+
}
|
|
72
|
+
for (const group of groups) {
|
|
73
|
+
const allKeys = [group.canonical, ...group.aliases];
|
|
74
|
+
const presentKeys = allKeys.filter(
|
|
75
|
+
(k) => Object.hasOwn(rawArgs, k) && rawArgs[k] !== undefined
|
|
76
|
+
);
|
|
77
|
+
if (presentKeys.length > 1) {
|
|
78
|
+
const firstKey = presentKeys[0];
|
|
79
|
+
const firstVal = rawArgs[firstKey];
|
|
80
|
+
for (let i = 1; i < presentKeys.length; i += 1) {
|
|
81
|
+
const otherKey = presentKeys[i];
|
|
82
|
+
if (!Object.is(firstVal, rawArgs[otherKey])) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
error: "CONFLICTING_PARAMETER_ALIASES",
|
|
86
|
+
canonicalKey: group.canonical,
|
|
87
|
+
conflictKey: otherKey,
|
|
88
|
+
message: `Invalid params: conflicting parameter aliases for '${group.canonical}' ('${firstKey}' vs '${otherKey}')`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (presentKeys.length >= 1) {
|
|
94
|
+
normalized[group.canonical] = rawArgs[presentKeys[0]];
|
|
95
|
+
for (const aliasKey of group.aliases) {
|
|
96
|
+
delete normalized[aliasKey];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { ok: true, args: normalized };
|
|
101
|
+
}
|
|
30
102
|
|
|
31
103
|
export function scanLocalMcpPayloadForSecrets(node, depth = 0) {
|
|
32
104
|
if (depth > 8) {
|
|
@@ -381,7 +453,28 @@ export const LOCAL_MCP_TOOLS = Object.freeze([
|
|
|
381
453
|
maximum: 2592000,
|
|
382
454
|
default: 259200,
|
|
383
455
|
},
|
|
384
|
-
events: {
|
|
456
|
+
events: {
|
|
457
|
+
type: "array",
|
|
458
|
+
items: {
|
|
459
|
+
type: "object",
|
|
460
|
+
properties: {
|
|
461
|
+
eventId: { type: "string" },
|
|
462
|
+
eventType: { type: "string" },
|
|
463
|
+
createdSec: { type: "integer" },
|
|
464
|
+
subscriptionStatus: { type: "string" },
|
|
465
|
+
tier: { type: "string" },
|
|
466
|
+
currentPeriodEndSec: { type: "integer" },
|
|
467
|
+
id: { type: "string" },
|
|
468
|
+
type: { type: "string" },
|
|
469
|
+
created: { type: "integer" },
|
|
470
|
+
status: { type: "string" },
|
|
471
|
+
target_status: { type: "string" },
|
|
472
|
+
arrivedAtSec: { type: "integer" },
|
|
473
|
+
arrived_at_sec: { type: "integer" },
|
|
474
|
+
},
|
|
475
|
+
additionalProperties: false,
|
|
476
|
+
},
|
|
477
|
+
},
|
|
385
478
|
demo: { type: "boolean" },
|
|
386
479
|
},
|
|
387
480
|
},
|
|
@@ -1148,14 +1241,33 @@ function evaluateLocalVisaCe3Qualification(disputeReason, disputedTransaction, p
|
|
|
1148
1241
|
}
|
|
1149
1242
|
|
|
1150
1243
|
export function executeLocalMcpTool(toolName, rawArgs = {}) {
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1244
|
+
const inputArgs =
|
|
1245
|
+
rawArgs === undefined
|
|
1246
|
+
? {}
|
|
1247
|
+
: rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs)
|
|
1248
|
+
? rawArgs
|
|
1249
|
+
: null;
|
|
1250
|
+
if (!inputArgs) {
|
|
1251
|
+
const err = new Error("Invalid params: arguments must be a JSON object");
|
|
1252
|
+
err.code = -32602;
|
|
1253
|
+
err.reason = "INVALID_ARGUMENTS_OBJECT";
|
|
1254
|
+
throw err;
|
|
1255
|
+
}
|
|
1256
|
+
const secretScan = scanLocalMcpPayloadForSecrets(inputArgs);
|
|
1153
1257
|
if (!secretScan.ok) {
|
|
1154
1258
|
const err = new Error(`Invalid params: ${secretScan.reason}`);
|
|
1155
1259
|
err.code = -32602;
|
|
1156
1260
|
err.reason = secretScan.reason;
|
|
1157
1261
|
throw err;
|
|
1158
1262
|
}
|
|
1263
|
+
const normalizedRes = normalizeMcpToolArgs(toolName, inputArgs);
|
|
1264
|
+
if (!normalizedRes.ok) {
|
|
1265
|
+
const err = new Error(normalizedRes.message);
|
|
1266
|
+
err.code = -32602;
|
|
1267
|
+
err.reason = normalizedRes.error;
|
|
1268
|
+
throw err;
|
|
1269
|
+
}
|
|
1270
|
+
const args = normalizedRes.args;
|
|
1159
1271
|
|
|
1160
1272
|
if (toolName === "revclamp_calculate_roi_and_plan_fit") {
|
|
1161
1273
|
const monthlyWonDisputeUsd = Number(args.monthlyWonDisputeUsd ?? 10_000);
|
|
@@ -2611,7 +2723,7 @@ function toSafeLocalRpcId(rawId) {
|
|
|
2611
2723
|
return null;
|
|
2612
2724
|
}
|
|
2613
2725
|
|
|
2614
|
-
export function executeLocalMcpJsonRpc(rpcRequest, packageVersion = "1.20.
|
|
2726
|
+
export function executeLocalMcpJsonRpc(rpcRequest, packageVersion = "1.20.8") {
|
|
2615
2727
|
if (!rpcRequest || typeof rpcRequest !== "object" || Array.isArray(rpcRequest)) {
|
|
2616
2728
|
return {
|
|
2617
2729
|
jsonrpc: "2.0",
|
|
@@ -2794,7 +2906,18 @@ export function executeLocalMcpJsonRpc(rpcRequest, packageVersion = "1.20.7") {
|
|
|
2794
2906
|
|
|
2795
2907
|
if (method === "tools/call") {
|
|
2796
2908
|
const toolName = String(rpcRequest?.params?.name || "").trim();
|
|
2797
|
-
const
|
|
2909
|
+
const rawToolArgs =
|
|
2910
|
+
rpcRequest?.params?.arguments === undefined ? {} : rpcRequest.params.arguments;
|
|
2911
|
+
if (!rawToolArgs || typeof rawToolArgs !== "object" || Array.isArray(rawToolArgs)) {
|
|
2912
|
+
return {
|
|
2913
|
+
jsonrpc: "2.0",
|
|
2914
|
+
id: safeId,
|
|
2915
|
+
error: {
|
|
2916
|
+
code: -32602,
|
|
2917
|
+
message: "Invalid params: arguments must be a JSON object",
|
|
2918
|
+
},
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2798
2921
|
const toolSpec = LOCAL_MCP_TOOLS.find((t) => t.name === toolName);
|
|
2799
2922
|
if (!toolSpec) {
|
|
2800
2923
|
return {
|
|
@@ -2807,6 +2930,24 @@ export function executeLocalMcpJsonRpc(rpcRequest, packageVersion = "1.20.7") {
|
|
|
2807
2930
|
};
|
|
2808
2931
|
}
|
|
2809
2932
|
|
|
2933
|
+
const normalizedRes = normalizeMcpToolArgs(toolName, rawToolArgs);
|
|
2934
|
+
if (!normalizedRes.ok) {
|
|
2935
|
+
return {
|
|
2936
|
+
jsonrpc: "2.0",
|
|
2937
|
+
id: safeId,
|
|
2938
|
+
error: {
|
|
2939
|
+
code: -32602,
|
|
2940
|
+
message: normalizedRes.message,
|
|
2941
|
+
data: {
|
|
2942
|
+
error: normalizedRes.error,
|
|
2943
|
+
reason: normalizedRes.error,
|
|
2944
|
+
...(normalizedRes.canonicalKey ? { canonicalKey: normalizedRes.canonicalKey } : {}),
|
|
2945
|
+
},
|
|
2946
|
+
},
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
const toolArgs = normalizedRes.args;
|
|
2950
|
+
|
|
2810
2951
|
const exampleCallMap = {
|
|
2811
2952
|
revclamp_verify_webhook_signature: {
|
|
2812
2953
|
provider: "stripe",
|
package/mcp-config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "revclamp-mcp",
|
|
3
3
|
"mcpName": "io.github.revclamp/revclamp-mcp",
|
|
4
|
-
"version": "1.20.
|
|
4
|
+
"version": "1.20.8",
|
|
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",
|
|
@@ -36,7 +36,10 @@
|
|
|
36
36
|
"homepage": "https://revclamp.com",
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "https://github.com/
|
|
39
|
+
"url": "https://github.com/lhphong09t2/revclamp-mcp.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/lhphong09t2/revclamp-mcp/issues"
|
|
40
43
|
},
|
|
41
44
|
"license": "MIT",
|
|
42
45
|
"engines": {
|
package/server.json
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
"description": "Zero-take-rate Stripe 2025-03-31.basil, Visa CE 3.0 & EU/UK VIES VAT guardrails MCP server.",
|
|
5
5
|
"status": "active",
|
|
6
6
|
"repository": {
|
|
7
|
-
"url": "https://github.com/
|
|
7
|
+
"url": "https://github.com/lhphong09t2/revclamp-mcp",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.20.
|
|
10
|
+
"version": "1.20.8",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registry_type": "npm",
|
|
14
14
|
"registry_base_url": "https://registry.npmjs.org",
|
|
15
15
|
"identifier": "revclamp-mcp",
|
|
16
|
-
"version": "1.20.
|
|
16
|
+
"version": "1.20.8",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
},
|