sigrank 0.0.176 → 0.0.177
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 +1 -1
- package/cli.mjs +5 -1
- package/package.json +1 -1
- package/submit.mjs +18 -0
- package/tools.mjs +88 -6
package/README.md
CHANGED
package/cli.mjs
CHANGED
|
@@ -236,7 +236,10 @@ async function fetchBoard(window = "30d") {
|
|
|
236
236
|
const res = await fetch(
|
|
237
237
|
`${DEFAULT_API_BASE}/api/v1/leaderboard?window=${window}&metric=yield_`,
|
|
238
238
|
{
|
|
239
|
-
headers: {
|
|
239
|
+
headers: {
|
|
240
|
+
accept: "application/json",
|
|
241
|
+
"user-agent": `node/${process.version} sigrank-mcp/cli`,
|
|
242
|
+
},
|
|
240
243
|
},
|
|
241
244
|
);
|
|
242
245
|
if (!res.ok) throw new Error(`Board API → HTTP ${res.status}`);
|
|
@@ -1472,6 +1475,7 @@ async function runSigRank() {
|
|
|
1472
1475
|
headers: {
|
|
1473
1476
|
"content-type": "application/json",
|
|
1474
1477
|
accept: "application/json",
|
|
1478
|
+
"user-agent": `node/${process.version} sigrank-mcp/cli`,
|
|
1475
1479
|
},
|
|
1476
1480
|
body: JSON.stringify({
|
|
1477
1481
|
codename,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigrank",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.177",
|
|
4
4
|
"author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
|
|
5
5
|
"description": "SigRank MCP server — the yield cascade + live leaderboard as MCP tools any agent can call",
|
|
6
6
|
"mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
|
package/submit.mjs
CHANGED
|
@@ -11,9 +11,22 @@
|
|
|
11
11
|
|
|
12
12
|
import { snapshotHash, signPayload } from "./sign.mjs";
|
|
13
13
|
import { preflight } from "./preflight.mjs";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
14
15
|
|
|
15
16
|
const WINDOW_TYPE = { "7d": "7d", "30d": "30d", "90d": "90d", all: "all_time" };
|
|
16
17
|
const WINDOW_SPAN_DAYS = { "7d": 7, "30d": 30, "90d": 90, all_time: 3650 };
|
|
18
|
+
|
|
19
|
+
/** Resolve this package's version for the User-Agent stamp (best-effort). */
|
|
20
|
+
function _pkgVersion() {
|
|
21
|
+
try {
|
|
22
|
+
const pkg = JSON.parse(
|
|
23
|
+
readFileSync(new URL("./package.json", import.meta.url), "utf-8"),
|
|
24
|
+
);
|
|
25
|
+
return pkg.version;
|
|
26
|
+
} catch {
|
|
27
|
+
return "unknown";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
17
30
|
const PLATFORM_ENUM = new Set([
|
|
18
31
|
"claude",
|
|
19
32
|
"chatgpt",
|
|
@@ -181,8 +194,13 @@ export async function submitSignedWindow(
|
|
|
181
194
|
((url, init = {}) => {
|
|
182
195
|
const ctrl = new AbortController();
|
|
183
196
|
const timer = setTimeout(() => ctrl.abort(), 15_000);
|
|
197
|
+
const headers = {
|
|
198
|
+
"user-agent": `node/${process.version} sigrank-mcp/${_pkgVersion()}`,
|
|
199
|
+
...(init.headers || {}),
|
|
200
|
+
};
|
|
184
201
|
return fetch(url, {
|
|
185
202
|
...init,
|
|
203
|
+
headers,
|
|
186
204
|
signal: init.signal || ctrl.signal,
|
|
187
205
|
}).finally(() => clearTimeout(timer));
|
|
188
206
|
});
|
package/tools.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import { ALL_PLATFORMS } from "./adapters.mjs";
|
|
|
26
26
|
import { ensureIdentity, recordEnrollment } from "./keystore.mjs";
|
|
27
27
|
import { submitSignedWindow } from "./submit.mjs";
|
|
28
28
|
import { createHash } from "node:crypto";
|
|
29
|
-
import { execFile } from "node:child_process";
|
|
29
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
30
30
|
import { existsSync, readFileSync } from "node:fs";
|
|
31
31
|
import os from "node:os";
|
|
32
32
|
import path from "node:path";
|
|
@@ -256,6 +256,56 @@ export const DEFAULT_FETCH_TIMEOUT =
|
|
|
256
256
|
* past this is malformed or abusive — reject cleanly before parsing/POSTing (E2 hardening). */
|
|
257
257
|
const MAX_INPUT = 1_000_000;
|
|
258
258
|
|
|
259
|
+
/** Resolve this package's version for the User-Agent stamp (best-effort). */
|
|
260
|
+
function agentVersionStamp() {
|
|
261
|
+
try {
|
|
262
|
+
const pkg = JSON.parse(
|
|
263
|
+
readFileSync(new URL("./package.json", import.meta.url), "utf-8"),
|
|
264
|
+
);
|
|
265
|
+
return pkg.version;
|
|
266
|
+
} catch {
|
|
267
|
+
return "unknown";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* curl-based fetch fallback for when Node's fetch is blocked by Vercel's bot
|
|
273
|
+
* protection (TLS fingerprinting). Returns a Response-like object with .ok,
|
|
274
|
+
* .status, .json(), .text(). Uses execFileSync for synchronous curl execution.
|
|
275
|
+
*/
|
|
276
|
+
function curlFetch(url, init = {}, timeoutMs = 10_000) {
|
|
277
|
+
const args = ["-s", "-S", "-w", "\n__HTTP_STATUS__%{http_code}", "--max-time", String(Math.ceil(timeoutMs / 1000))];
|
|
278
|
+
if (init.method) args.push("-X", init.method);
|
|
279
|
+
const headers = init.headers || {};
|
|
280
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
281
|
+
args.push("-H", `${k}: ${v}`);
|
|
282
|
+
}
|
|
283
|
+
if (init.body) {
|
|
284
|
+
args.push("-d", init.body);
|
|
285
|
+
}
|
|
286
|
+
args.push(url);
|
|
287
|
+
let stdout;
|
|
288
|
+
try {
|
|
289
|
+
stdout = execFileSync("curl", args, {
|
|
290
|
+
encoding: "utf-8",
|
|
291
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
292
|
+
timeout: timeoutMs + 2000,
|
|
293
|
+
});
|
|
294
|
+
} catch (e) {
|
|
295
|
+
throw new Error(`curl transport failed: ${e.message}`);
|
|
296
|
+
}
|
|
297
|
+
// Parse the status code from the trailer
|
|
298
|
+
const statusMatch = stdout.match(/__HTTP_STATUS__(\d+)$/);
|
|
299
|
+
const status = statusMatch ? parseInt(statusMatch[1], 10) : 0;
|
|
300
|
+
const bodyText = statusMatch ? stdout.slice(0, statusMatch.index) : stdout;
|
|
301
|
+
return {
|
|
302
|
+
ok: status >= 200 && status < 300,
|
|
303
|
+
status,
|
|
304
|
+
json: async () => { try { return JSON.parse(bodyText); } catch { return {}; } },
|
|
305
|
+
text: async () => bodyText,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
259
309
|
// ── Smithery quality: output schemas + annotations ──────────────────────────
|
|
260
310
|
// MCP tool annotations hint to clients about side-effects, read-only status, etc.
|
|
261
311
|
const ANNOTATIONS = {
|
|
@@ -1357,13 +1407,45 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1357
1407
|
const rawFetch = opts.fetchImpl || fetch;
|
|
1358
1408
|
|
|
1359
1409
|
// Wrap every fetch with an AbortController timeout so a hung network call never
|
|
1360
|
-
// blocks the MCP client indefinitely.
|
|
1361
|
-
|
|
1410
|
+
// blocks the MCP client indefinitely. Vercel's bot protection (security
|
|
1411
|
+
// checkpoint) blocks Node's fetch via TLS fingerprinting — no UA fix helps.
|
|
1412
|
+
// When fetch gets a 403 Vercel challenge, fall back to curl (which passes).
|
|
1413
|
+
const doFetch = async (url, init = {}) => {
|
|
1362
1414
|
const ac = new AbortController();
|
|
1363
1415
|
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1416
|
+
const headers = {
|
|
1417
|
+
"user-agent": `node/${process.version} sigrank-mcp/${agentVersionStamp()}`,
|
|
1418
|
+
...(init.headers || {}),
|
|
1419
|
+
};
|
|
1420
|
+
try {
|
|
1421
|
+
const res = await rawFetch(url, { ...init, headers, signal: ac.signal });
|
|
1422
|
+
// Detect Vercel security checkpoint (403 + HTML body, not JSON)
|
|
1423
|
+
if (res.status === 403) {
|
|
1424
|
+
const text = await res.text();
|
|
1425
|
+
if (text.includes("Vercel Security Checkpoint") || text.includes("x-vercel-challenge")) {
|
|
1426
|
+
// Fall back to curl transport
|
|
1427
|
+
return curlFetch(url, init, timeoutMs);
|
|
1428
|
+
}
|
|
1429
|
+
// Real 403 from the API — return a mock response object
|
|
1430
|
+
return {
|
|
1431
|
+
ok: false,
|
|
1432
|
+
status: 403,
|
|
1433
|
+
json: async () => { try { return JSON.parse(text); } catch { return {}; } },
|
|
1434
|
+
text: async () => text,
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
return res;
|
|
1438
|
+
} catch (e) {
|
|
1439
|
+
if (e.name === "AbortError") throw e;
|
|
1440
|
+
// Network error — try curl as fallback
|
|
1441
|
+
try {
|
|
1442
|
+
return await curlFetch(url, init, timeoutMs);
|
|
1443
|
+
} catch {
|
|
1444
|
+
throw e;
|
|
1445
|
+
}
|
|
1446
|
+
} finally {
|
|
1447
|
+
clearTimeout(timer);
|
|
1448
|
+
}
|
|
1367
1449
|
};
|
|
1368
1450
|
|
|
1369
1451
|
const fetchJson = async (path) => {
|