@venturekit/mcp 0.0.50 → 0.0.51

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/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ export { handleMcpRequest } from './server.js';
28
28
  export { zodToJsonSchema, type JsonSchema } from './json-schema.js';
29
29
  export { McpError, type McpErrorOptions } from './errors.js';
30
30
  export { generateBearerToken, bearerTokenPrefix, parseScopedToken, parseBearer, DEFAULT_PREFIX_LENGTH, type GenerateBearerTokenOptions, type GeneratedBearerToken, type ParsedScopedToken, } from './tokens.js';
31
- export { createMcpToken, listMcpTokens, revokeMcpToken, verifyMcpToken, hashTokenSecret, verifyTokenSecret, type Querier, type McpTokenRow, type CreateMcpTokenInput, type CreatedMcpToken, } from './store.js';
31
+ export { createMcpToken, listMcpTokens, revokeMcpToken, verifyMcpToken, hashTokenSecret, tokenLookupHash, verifyTokenSecret, type Querier, type McpTokenRow, type CreateMcpTokenInput, type CreatedMcpToken, } from './store.js';
32
32
  export { getMcpMigrationsDir } from './path.js';
33
33
  export type { JsonRpcError, JsonRpcRequest, JsonRpcResponse, McpAuthResult, McpHandlerInput, McpHandlerOutput, McpServerConfig, McpServerInfo, McpTool, McpToolCallInfo, McpToolRegistry, } from './types.js';
34
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EACV,YAAY,EACZ,cAAc,EACd,eAAe,EACf,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,OAAO,EACP,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,YAAY,EACV,YAAY,EACZ,cAAc,EACd,eAAe,EACf,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,OAAO,EACP,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -28,6 +28,6 @@ export { handleMcpRequest } from './server.js';
28
28
  export { zodToJsonSchema } from './json-schema.js';
29
29
  export { McpError } from './errors.js';
30
30
  export { generateBearerToken, bearerTokenPrefix, parseScopedToken, parseBearer, DEFAULT_PREFIX_LENGTH, } from './tokens.js';
31
- export { createMcpToken, listMcpTokens, revokeMcpToken, verifyMcpToken, hashTokenSecret, verifyTokenSecret, } from './store.js';
31
+ export { createMcpToken, listMcpTokens, revokeMcpToken, verifyMcpToken, hashTokenSecret, tokenLookupHash, verifyTokenSecret, } from './store.js';
32
32
  export { getMcpMigrationsDir } from './path.js';
33
33
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAmB,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAwB,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,GAItB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,iBAAiB,GAKlB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAmB,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAwB,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,GAItB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,eAAe,EACf,iBAAiB,GAKlB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,18 @@
1
+ -- @venturekit/mcp — direct token lookup.
2
+ --
3
+ -- A token is 256 bits from a CSPRNG, so its SHA-256 is as unguessable as
4
+ -- the token itself and needs no slow hash. Looking a token up by that
5
+ -- digest is one indexed read; the former `(scope, prefix)` narrowing ran
6
+ -- scrypt once per candidate — and every token of a scope shares the
7
+ -- prefix, so an unauthenticated request could make the server run scrypt
8
+ -- once per token the scope holds.
9
+ --
10
+ -- Rows issued before this migration have no digest yet: the verifier
11
+ -- falls back to the scrypt path for them (bounded) and records the digest
12
+ -- on their first successful use.
13
+
14
+ ALTER TABLE vk_mcp_tokens ADD COLUMN IF NOT EXISTS lookup_hash text;
15
+
16
+ CREATE UNIQUE INDEX IF NOT EXISTS vk_mcp_tokens_lookup_hash_idx
17
+ ON vk_mcp_tokens (lookup_hash)
18
+ WHERE lookup_hash IS NOT NULL;
package/dist/store.d.ts CHANGED
@@ -9,13 +9,19 @@
9
9
  * to whatever it owns — a tenant slug, a user id, a project id.
10
10
  *
11
11
  * # Security
12
- * - Only a scrypt hash + the lookup `prefix` are persisted. The
12
+ * - Only digests are persisted (a SHA-256 lookup hash, a scrypt hash) + the `prefix`. The
13
13
  * plaintext is returned once from {@link createMcpToken} and never
14
14
  * stored.
15
- * - {@link verifyMcpToken} narrows by `(scope, prefix)` then runs a
16
- * constant-time scrypt compare over the (normally single) candidate.
15
+ * - {@link verifyMcpToken} looks the token up by its SHA-256 (one indexed read); rows from
16
+ * before the digest existed fall back to a bounded scrypt compare.
17
17
  * - Hashing is `node:crypto` scrypt — no third-party dependency.
18
18
  */
19
+ /**
20
+ * The token's lookup key: SHA-256 of the plaintext. A token carries 256
21
+ * bits of CSPRNG entropy, so its digest needs no slow hash to resist
22
+ * guessing, and an equality lookup on it costs one indexed read.
23
+ */
24
+ export declare function tokenLookupHash(plaintext: string): string;
19
25
  /**
20
26
  * Minimal querier shape — a function that runs a parameterized SQL
21
27
  * statement and returns rows. Matches `@venturekit/data`'s `Querier`
@@ -36,6 +42,8 @@ export interface McpTokenRow {
36
42
  prefix: string;
37
43
  /** scrypt hash (`scrypt$N$r$p$salt$dk`). */
38
44
  tokenHash: string;
45
+ /** SHA-256 of the plaintext ({@link tokenLookupHash}); `null` on rows issued before it existed. */
46
+ lookupHash?: string | null;
39
47
  /** Comma-separated tool whitelist; `null` = every tool. */
40
48
  allowedTools: string | null;
41
49
  lastUsedAt: Date | null;
@@ -72,11 +80,13 @@ export declare function createMcpToken(q: Querier, input: CreateMcpTokenInput):
72
80
  export declare function revokeMcpToken(q: Querier, scope: string, id: string): Promise<McpTokenRow | null>;
73
81
  /**
74
82
  * Resolve a plaintext bearer token to its active row, or `null` if the
75
- * token is malformed, unknown, or revoked. Parses the scope from the
76
- * token, narrows by `(scope, prefix)`, scrypt-verifies each candidate,
77
- * and stamps `last_used_at` on a match (best-effort; failures don't
78
- * block auth). The host is responsible for any further checks (e.g.
79
- * mapping `scope` to a tenant and rejecting suspended ones).
83
+ * token is malformed, unknown, or revoked. Looks the token up by its
84
+ * SHA-256 ({@link tokenLookupHash}) — one indexed read, no hashing per
85
+ * candidate — and stamps `last_used_at` on a match (best-effort; failures
86
+ * don't block auth). Rows issued before the digest existed are verified
87
+ * with scrypt (at most {@link MAX_LEGACY_CANDIDATES}) and get their digest
88
+ * recorded on first use. The host is responsible for any further checks
89
+ * (e.g. mapping `scope` to a tenant and rejecting suspended ones).
80
90
  */
81
91
  export declare function verifyMcpToken(q: Querier, plaintext: string): Promise<McpTokenRow | null>;
82
92
  //# sourceMappingURL=store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAsBH;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAClD,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhB;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC/B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,WAAW,CAAC;IACjB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;CACf;AAID,8EAA8E;AAC9E,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQxE;AAED,yEAAyE;AACzE,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAY3F;AAID,mEAAmE;AACnE,wBAAsB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAKrF;AAID,kFAAkF;AAClF,wBAAsB,cAAc,CAClC,CAAC,EAAE,OAAO,EACV,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,eAAe,CAAC,CAe1B;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,CAAC,EAAE,OAAO,EACV,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAS7B;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAoB/F"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAeH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAaD;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAClD,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhB;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,mGAAmG;IACnG,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2DAA2D;IAC3D,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC/B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,WAAW,CAAC;IACjB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;CACf;AAID,8EAA8E;AAC9E,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQxE;AAED,yEAAyE;AACzE,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAY3F;AAID,mEAAmE;AACnE,wBAAsB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAKrF;AAID,kFAAkF;AAClF,wBAAsB,cAAc,CAClC,CAAC,EAAE,OAAO,EACV,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,eAAe,CAAC,CAe1B;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,CAAC,EAAE,OAAO,EACV,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAS7B;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAgC/F"}
package/dist/store.js CHANGED
@@ -9,17 +9,27 @@
9
9
  * to whatever it owns — a tenant slug, a user id, a project id.
10
10
  *
11
11
  * # Security
12
- * - Only a scrypt hash + the lookup `prefix` are persisted. The
12
+ * - Only digests are persisted (a SHA-256 lookup hash, a scrypt hash) + the `prefix`. The
13
13
  * plaintext is returned once from {@link createMcpToken} and never
14
14
  * stored.
15
- * - {@link verifyMcpToken} narrows by `(scope, prefix)` then runs a
16
- * constant-time scrypt compare over the (normally single) candidate.
15
+ * - {@link verifyMcpToken} looks the token up by its SHA-256 (one indexed read); rows from
16
+ * before the digest existed fall back to a bounded scrypt compare.
17
17
  * - Hashing is `node:crypto` scrypt — no third-party dependency.
18
18
  */
19
- import { randomBytes, scrypt as scryptCb, timingSafeEqual, } from 'node:crypto';
19
+ import { createHash, randomBytes, scrypt as scryptCb, timingSafeEqual, } from 'node:crypto';
20
20
  import { promisify } from 'node:util';
21
21
  import { generateBearerToken, bearerTokenPrefix, parseScopedToken } from './tokens.js';
22
22
  const scrypt = promisify(scryptCb);
23
+ /**
24
+ * The token's lookup key: SHA-256 of the plaintext. A token carries 256
25
+ * bits of CSPRNG entropy, so its digest needs no slow hash to resist
26
+ * guessing, and an equality lookup on it costs one indexed read.
27
+ */
28
+ export function tokenLookupHash(plaintext) {
29
+ return createHash('sha256').update(plaintext, 'utf8').digest('hex');
30
+ }
31
+ /** Legacy rows (no lookup hash yet) verified per request, at most — bounds the scrypt work. */
32
+ const MAX_LEGACY_CANDIDATES = 3;
23
33
  // scrypt cost parameters. N=16384 keeps a single hash ~50-80ms (on par
24
34
  // with bcrypt cost 12) while staying under Node's default 32MB maxmem
25
35
  // (128 * N * r = 16MB).
@@ -66,9 +76,9 @@ export async function createMcpToken(q, input) {
66
76
  const { token, prefix } = generateBearerToken({ scope: input.scope });
67
77
  const tokenHash = await hashTokenSecret(token);
68
78
  const allowedTools = input.allowedTools && input.allowedTools.length > 0 ? input.allowedTools.join(',') : null;
69
- const rows = await q(`INSERT INTO vk_mcp_tokens (scope, label, prefix, token_hash, allowed_tools, created_by)
70
- VALUES ($1, $2, $3, $4, $5, $6)
71
- RETURNING *`, [input.scope, input.label, prefix, tokenHash, allowedTools, input.createdBy ?? null]);
79
+ const rows = await q(`INSERT INTO vk_mcp_tokens (scope, label, prefix, token_hash, lookup_hash, allowed_tools, created_by)
80
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
81
+ RETURNING *`, [input.scope, input.label, prefix, tokenHash, tokenLookupHash(token), allowedTools, input.createdBy ?? null]);
72
82
  const row = rows[0];
73
83
  if (!row)
74
84
  throw new Error('[mcp/createMcpToken] INSERT ... RETURNING returned no rows');
@@ -87,25 +97,38 @@ export async function revokeMcpToken(q, scope, id) {
87
97
  }
88
98
  /**
89
99
  * Resolve a plaintext bearer token to its active row, or `null` if the
90
- * token is malformed, unknown, or revoked. Parses the scope from the
91
- * token, narrows by `(scope, prefix)`, scrypt-verifies each candidate,
92
- * and stamps `last_used_at` on a match (best-effort; failures don't
93
- * block auth). The host is responsible for any further checks (e.g.
94
- * mapping `scope` to a tenant and rejecting suspended ones).
100
+ * token is malformed, unknown, or revoked. Looks the token up by its
101
+ * SHA-256 ({@link tokenLookupHash}) — one indexed read, no hashing per
102
+ * candidate — and stamps `last_used_at` on a match (best-effort; failures
103
+ * don't block auth). Rows issued before the digest existed are verified
104
+ * with scrypt (at most {@link MAX_LEGACY_CANDIDATES}) and get their digest
105
+ * recorded on first use. The host is responsible for any further checks
106
+ * (e.g. mapping `scope` to a tenant and rejecting suspended ones).
95
107
  */
96
108
  export async function verifyMcpToken(q, plaintext) {
97
109
  const parsed = parseScopedToken(plaintext);
98
110
  if (!parsed)
99
111
  return null;
100
- const prefix = bearerTokenPrefix(plaintext);
101
- const candidates = await q(`SELECT * FROM vk_mcp_tokens WHERE scope = $1 AND prefix = $2 AND revoked_at IS NULL`, [parsed.scope, prefix]);
102
- for (const row of candidates) {
112
+ const lookupHash = tokenLookupHash(plaintext);
113
+ const [direct] = await q(`SELECT * FROM vk_mcp_tokens WHERE lookup_hash = $1 AND scope = $2 AND revoked_at IS NULL`, [lookupHash, parsed.scope]);
114
+ if (direct) {
115
+ touch(q, `UPDATE vk_mcp_tokens SET last_used_at = NOW() WHERE id = $1`, [direct.id]);
116
+ return direct;
117
+ }
118
+ const legacy = await q(`SELECT * FROM vk_mcp_tokens
119
+ WHERE scope = $1 AND prefix = $2 AND lookup_hash IS NULL AND revoked_at IS NULL
120
+ ORDER BY created_at DESC
121
+ LIMIT ${MAX_LEGACY_CANDIDATES}`, [parsed.scope, bearerTokenPrefix(plaintext)]);
122
+ for (const row of legacy) {
103
123
  if (await verifyTokenSecret(plaintext, row.tokenHash)) {
104
- // Best-effort touch — never block a valid auth on a stats write.
105
- Promise.resolve(q(`UPDATE vk_mcp_tokens SET last_used_at = NOW() WHERE id = $1`, [row.id])).catch(() => { });
124
+ touch(q, `UPDATE vk_mcp_tokens SET lookup_hash = $2, last_used_at = NOW() WHERE id = $1 AND lookup_hash IS NULL`, [row.id, lookupHash]);
106
125
  return row;
107
126
  }
108
127
  }
109
128
  return null;
110
129
  }
130
+ /** A bookkeeping write that must never block or fail authentication. */
131
+ function touch(q, sql, params) {
132
+ Promise.resolve(q(sql, params)).catch(() => { });
133
+ }
111
134
  //# sourceMappingURL=store.js.map
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venturekit/mcp",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "description": "Transport-agnostic Model Context Protocol (MCP) server primitives for VentureKit: JSON-RPC dispatch, a typed tool registry, and a Zod→JSON-Schema serializer.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -41,12 +41,12 @@
41
41
  "agents"
42
42
  ],
43
43
  "peerDependencies": {
44
- "zod": "^4.5.4"
44
+ "zod": "^4.6.6"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^26.5.1",
48
48
  "typescript": "^5.3.0",
49
- "zod": "^4.5.4"
49
+ "zod": "^4.6.6"
50
50
  },
51
51
  "scripts": {
52
52
  "build": "tsc && node -e \"require('fs').cpSync('src/migrations','dist/migrations',{recursive:true})\"",
@@ -0,0 +1,18 @@
1
+ -- @venturekit/mcp — direct token lookup.
2
+ --
3
+ -- A token is 256 bits from a CSPRNG, so its SHA-256 is as unguessable as
4
+ -- the token itself and needs no slow hash. Looking a token up by that
5
+ -- digest is one indexed read; the former `(scope, prefix)` narrowing ran
6
+ -- scrypt once per candidate — and every token of a scope shares the
7
+ -- prefix, so an unauthenticated request could make the server run scrypt
8
+ -- once per token the scope holds.
9
+ --
10
+ -- Rows issued before this migration have no digest yet: the verifier
11
+ -- falls back to the scrypt path for them (bounded) and records the digest
12
+ -- on their first successful use.
13
+
14
+ ALTER TABLE vk_mcp_tokens ADD COLUMN IF NOT EXISTS lookup_hash text;
15
+
16
+ CREATE UNIQUE INDEX IF NOT EXISTS vk_mcp_tokens_lookup_hash_idx
17
+ ON vk_mcp_tokens (lookup_hash)
18
+ WHERE lookup_hash IS NOT NULL;