chainhint-mcp 1.3.4 → 1.4.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
@@ -104,11 +104,13 @@ npm run dev
104
104
 
105
105
  ## Environment Variables
106
106
 
107
+ > **Upgrade to 1.4.0.** Versions before 1.4.0 send ChainHint's legacy Supabase anon key, which is being retired: `get_trace_status` on those versions will return 401. `check_wallet_risk` and `lookup_address` are unaffected. `npx chainhint-mcp@latest` picks up 1.4.0.
108
+
107
109
  | Variable | Required | Description |
108
110
  |----------|----------|-------------|
109
111
  | `CHAINHINT_API_KEY` | — | Agency plan key from chainhint.com. Lifts the free limits (3 checks + 10 lookups per day) to 10,000 / day |
110
112
  | `CHAINHINT_API_URL` | — | Override API base URL (default: production) |
111
- | `CHAINHINT_SUPABASE_ANON_KEY` | — | Override anon key for get_trace_status (public incidents) |
113
+ | `CHAINHINT_SUPABASE_PUBLISHABLE_KEY` | — | Override the Supabase publishable key used by get_trace_status (public incidents). Sent only in `apikey`. The pre-1.4.0 name `CHAINHINT_SUPABASE_ANON_KEY` is still read |
112
114
 
113
115
  ## Example prompts
114
116
 
package/dist/index.js CHANGED
@@ -22,12 +22,14 @@ import { evidenceLines } from "./evidence.js";
22
22
  import { entityLine } from "./entity.js";
23
23
  import { ADDRESS_FORMS, normalizeAddr } from "./address.js";
24
24
  import { DATABASE_CANON } from "./canon.js";
25
+ import { DEFAULT_SUPABASE_KEY, isSupabaseKeyConfigured, resolveSupabaseKey, supabaseRestHeaders } from "./supabase-rest.js";
25
26
  // ── Config ────────────────────────────────────────────────────────────────────
26
27
  const API_KEY = process.env.CHAINHINT_API_KEY;
27
28
  const BASE_URL = process.env.CHAINHINT_API_URL ?? "https://kjiwfwymnuzxriokhcjk.supabase.co/functions/v1";
28
29
  const SUPABASE_URL = process.env.CHAINHINT_SUPABASE_URL ?? "https://kjiwfwymnuzxriokhcjk.supabase.co";
29
- const SUPABASE_ANON_KEY = process.env.CHAINHINT_SUPABASE_ANON_KEY ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtqaXdmd3ltbnV6eHJpb2toY2prIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI3MzkxODgsImV4cCI6MjA4ODMxNTE4OH0.VqzzF_jI8zF072cbjWEDbYo3PnMDlIPy621iWkXEqyo";
30
- const VERSION = "1.3.4";
30
+ // Publishable key, sent only in `apikey` (never as a Bearer token) — see supabase-rest.ts.
31
+ const SUPABASE_KEY = resolveSupabaseKey(process.env, DEFAULT_SUPABASE_KEY);
32
+ const VERSION = "1.4.0";
31
33
  const USER_AGENT = `chainhint-mcp/${VERSION}`;
32
34
  const FREE_CHECKS_PER_DAY = 3;
33
35
  const UPGRADE_HINT = "set CHAINHINT_API_KEY (Agency plan, https://chainhint.com/pricing) for 10,000/day";
@@ -78,12 +80,11 @@ async function supabaseGet(table, params) {
78
80
  for (const [k, v] of Object.entries(params)) {
79
81
  url.searchParams.set(k, v);
80
82
  }
83
+ if (!isSupabaseKeyConfigured(SUPABASE_KEY)) {
84
+ throw new Error("get_trace_status is not configured: no Supabase publishable key (set CHAINHINT_SUPABASE_PUBLISHABLE_KEY or upgrade chainhint-mcp)");
85
+ }
81
86
  const res = await fetch(url.toString(), {
82
- headers: {
83
- "apikey": SUPABASE_ANON_KEY,
84
- "Authorization": `Bearer ${SUPABASE_ANON_KEY}`,
85
- "Accept": "application/json",
86
- },
87
+ headers: supabaseRestHeaders(SUPABASE_KEY),
87
88
  });
88
89
  if (!res.ok) {
89
90
  const err = await res.text();
@@ -0,0 +1,21 @@
1
+ /** Env var for the project key (1.4.0+). */
2
+ export declare const PUBLISHABLE_KEY_ENV = "CHAINHINT_SUPABASE_PUBLISHABLE_KEY";
3
+ /** Pre-1.4.0 name, still honoured so existing configs keep their override. */
4
+ export declare const LEGACY_ANON_KEY_ENV = "CHAINHINT_SUPABASE_ANON_KEY";
5
+ /** Env override first (new name, then the legacy name), else the built-in default. */
6
+ export declare function resolveSupabaseKey(env: Record<string, string | undefined>, fallback: string): string;
7
+ /** Headers for an anonymous REST read of a public view: key in apikey only. */
8
+ export declare function supabaseRestHeaders(key: string): Record<string, string>;
9
+ /** Marker the default carries until the owner pastes the real publishable key. */
10
+ export declare const PUBLISHABLE_KEY_PLACEHOLDER = "sb_publishable_PASTE_BEFORE_PUBLISH";
11
+ /**
12
+ * ChainHint's Supabase publishable key (sb_publishable_…) — public by design, the
13
+ * same value the chainhint.com bundle ships. Paste it here before `npm publish`;
14
+ * `prepublishOnly` (scripts/check-publish.mjs) refuses to publish while this is the
15
+ * placeholder, a JWT or anything that is not a publishable key.
16
+ */
17
+ export declare const DEFAULT_SUPABASE_KEY: string;
18
+ /** Why `key` must not be the published default, or null when it is a real publishable key. */
19
+ export declare function publishKeyProblem(key: string): string | null;
20
+ /** true when REST reads can run: an override or a real default. */
21
+ export declare function isSupabaseKeyConfigured(key: string): boolean;
@@ -0,0 +1,44 @@
1
+ // KIR-81 (1.4.0): the Supabase project key is a publishable key
2
+ // (`sb_publishable_…`), not a JWT. It is sent ONLY in the `apikey` header —
3
+ // never as `Authorization: Bearer`, which PostgREST would try to verify as a
4
+ // JWT. Versions < 1.4.0 sent the legacy anon JWT in both headers and stop
5
+ // working when the project disables its legacy API keys.
6
+ /** Env var for the project key (1.4.0+). */
7
+ export const PUBLISHABLE_KEY_ENV = "CHAINHINT_SUPABASE_PUBLISHABLE_KEY";
8
+ /** Pre-1.4.0 name, still honoured so existing configs keep their override. */
9
+ export const LEGACY_ANON_KEY_ENV = "CHAINHINT_SUPABASE_ANON_KEY";
10
+ /** Env override first (new name, then the legacy name), else the built-in default. */
11
+ export function resolveSupabaseKey(env, fallback) {
12
+ const fromEnv = env[PUBLISHABLE_KEY_ENV] || env[LEGACY_ANON_KEY_ENV];
13
+ return fromEnv && fromEnv.trim() ? fromEnv.trim() : fallback;
14
+ }
15
+ /** Headers for an anonymous REST read of a public view: key in apikey only. */
16
+ export function supabaseRestHeaders(key) {
17
+ return { apikey: key, Accept: "application/json" };
18
+ }
19
+ /** Marker the default carries until the owner pastes the real publishable key. */
20
+ export const PUBLISHABLE_KEY_PLACEHOLDER = "sb_publishable_PASTE_BEFORE_PUBLISH";
21
+ /**
22
+ * ChainHint's Supabase publishable key (sb_publishable_…) — public by design, the
23
+ * same value the chainhint.com bundle ships. Paste it here before `npm publish`;
24
+ * `prepublishOnly` (scripts/check-publish.mjs) refuses to publish while this is the
25
+ * placeholder, a JWT or anything that is not a publishable key.
26
+ */
27
+ export const DEFAULT_SUPABASE_KEY = "sb_publishable_BpalXiQKLHEpDqyNpt2Tkw_JYkuIGBK";
28
+ /** Why `key` must not be the published default, or null when it is a real publishable key. */
29
+ export function publishKeyProblem(key) {
30
+ const k = (key ?? "").trim();
31
+ if (k === PUBLISHABLE_KEY_PLACEHOLDER)
32
+ return "the default key is still the placeholder";
33
+ if (k.startsWith("eyJ"))
34
+ return "the default key is a JWT (legacy anon key) — use the sb_publishable_ key";
35
+ if (k.startsWith("sb_secret_"))
36
+ return "the default key is a SECRET key — never ship it";
37
+ if (!/^sb_publishable_[A-Za-z0-9_-]{16,}$/.test(k))
38
+ return "the default key does not look like an sb_publishable_ key";
39
+ return null;
40
+ }
41
+ /** true when REST reads can run: an override or a real default. */
42
+ export function isSupabaseKeyConfigured(key) {
43
+ return publishKeyProblem(key) === null || ((key ?? "").trim() !== "" && (key ?? "").trim() !== PUBLISHABLE_KEY_PLACEHOLDER && !(key ?? "").trim().startsWith("eyJ"));
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainhint-mcp",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "ChainHint MCP server — crypto risk intelligence tools for Claude Desktop and Cursor",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,8 +35,9 @@
35
35
  "build": "tsc",
36
36
  "dev": "tsx src/index.ts",
37
37
  "start": "node dist/index.js",
38
- "prepublishOnly": "npm run build",
39
- "test": "npm run build && node --test test/*.test.mjs"
38
+ "prepublishOnly": "npm run build && node scripts/check-publish.mjs",
39
+ "test": "npm run build && node --test test/*.test.mjs",
40
+ "check:publish": "npm run build && node scripts/check-publish.mjs"
40
41
  },
41
42
  "dependencies": {
42
43
  "@modelcontextprotocol/sdk": "^1.0.0",