@vllnt/convex-helpers 0.1.0 → 0.1.1-canary.ba6c009

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/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Fixed
11
+
12
+ - Fail closed on thrown or stalled MCP authorization hooks; bound auth/lifecycle
13
+ callbacks to 10 seconds and remove raw exception logging.
14
+ - Add optional host-configured `AuthCtx.trustedIssuer` enforcement before identity
15
+ row callbacks, preserving the legacy single-issuer contract when omitted.
16
+ - Reject unsafe preview patterns, localhost paths/normalization and control characters;
17
+ validate cookie origins instead of trusting a prefix.
18
+ - Bound retargeting to 1000 rows with sequential callbacks and failure short-circuiting.
19
+ - Remove release-note shell interpolation; gate stable dispatch to main and explicit
20
+ `RELEASE_ENABLED=true`, and remove workflow-wide write/OIDC privileges.
21
+ - Patch development Handlebars and Vite/esbuild advisory paths; document the remaining
22
+ deepmerge-ts tooling advisory in `docs/READINESS.md`.
23
+
24
+ - Convert MCP `int64` and `bytes` inputs to native Convex values and serialize native Convex
25
+ results with lossless tagged JSON.
26
+ - Enforce resource argument validators before dispatch and reject unsafe mutation/action
27
+ timeouts that could report failure while non-cancellable side effects continue.
28
+
29
+ ### Changed
30
+
31
+ - Document actual shipped APIs and host-owned identity/merge/CSRF boundaries; identity
32
+ integration fixtures now receive strict lint checks (legacy MCP fixtures remain excluded).
33
+
34
+ - Refresh all direct dependencies to their latest compatible releases for canary validation.
35
+ - Require `convex@^1.45.0` and update `convex-test` to `^0.0.56`.
36
+
37
+ ### Added
38
+
39
+ - `./better-auth` — fail-closed trusted-origin parser + HTTPS cookie settings for `@convex-dev/better-auth` (does not import better-auth).
40
+ - `./identity` — `getOrCreateFromAuth` / `requireFromAuth` / `retargetRows` host-`ctx` glue for anonymous sessions and anonymous→identified upgrades.
41
+ - `./mcp` entry — expose Convex functions as MCP tools (`createMCPServer` + `query`/`mutation`/`action`/`resource`), default-deny auth, lifecycle hooks, validator→zod conversion, and optional HMAC-signed cursor pagination. Absorbed from `@vllnt/convex-mcp` (see ROADMAP `absorb-convex-mcp`). `@modelcontextprotocol/sdk` and `zod` are optional peer deps — the `./mcp` entry is tree-shakeable, so backend-only consumers pull neither. 100% test coverage retained (161 tests).
42
+
43
+ ## [0.1.0] - 2026-06-13
44
+
45
+ ### Added
46
+
47
+ - Scaffolded to fleet Universal standard (type-B helpers library)
48
+ - Seed root utilities: `asyncMap`, `pruneNull`, `nullThrows`, `NullDocumentError`
49
+ - 100% test coverage gate via vitest thresholds
50
+ - CI workflow, publish workflow, email-guard workflow
51
+ - Full docs set: README, CHANGELOG, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT
52
+ - `llms.txt` and `llms-full.txt` for AI agent discovery
package/README.md CHANGED
@@ -5,8 +5,8 @@
5
5
 
6
6
  # @vllnt/convex-helpers
7
7
 
8
- Typed host-side helpers for Convex backends — builders, errors, validators, relationships,
9
- pagination, HTTP, env, and observability.
8
+ Typed Convex host-side utilities, trusted-origin and cookie configuration helpers,
9
+ identity-row glue, and optional MCP tools.
10
10
 
11
11
  ```ts
12
12
  import { asyncMap, pruneNull, nullThrows } from "@vllnt/convex-helpers";
@@ -26,10 +26,11 @@ See [ROADMAP.md](./ROADMAP.md) for the planned module surface.
26
26
  - `asyncMap` — parallel async map that preserves order
27
27
  - `pruneNull` — filter null/undefined from arrays
28
28
  - `nullThrows` — non-null assertion with typed `NullDocumentError`
29
+ - `./better-auth` — fail-closed trusted origins + HTTPS cookies for better-auth
30
+ - `./identity` — identity-row lookup/bootstrap and bounded row-callback counting
29
31
  - `./mcp` — expose Convex functions as MCP tools (`createMCPServer` + `query`/`mutation`/`action`/`resource`), default-deny auth, optional cursor pagination
30
32
  - [planned] `./builders` — `customQuery`/`customMutation`/`customAction`/`customCtx`
31
33
  - [planned] `./errors` — typed `AppError` + HTTP-status map
32
- - [planned] `./auth` — provider-agnostic `requireIdentity`/`getCurrentSubject`
33
34
  - [planned] `./env` — `defineEnv(zodSchema)` cold-start validation
34
35
  - [planned] `./tracing` — span emit + `traceparent` propagation via `@vllnt/logger`
35
36
  - [planned] `./testing` — fixture factories + `withIdentity`
@@ -89,6 +90,78 @@ export const mcp = createMCPServer({
89
90
  export const { GET, POST } = mcp.handler();
90
91
  ```
91
92
 
93
+ MCP auth and lifecycle callbacks have a 10-second deadline per callback. A thrown
94
+ or timed-out `before` hook denies dispatch with a generic message. Auth failures
95
+ also deny access generically. Success/error hooks cannot undo completed dispatch.
96
+ Timeouts stop waiting, not the host callback; callbacks must avoid side effects.
97
+ Raw errors are not logged by the library; hosts may observe hook context and must
98
+ redact errors, arguments and credentials before logging. Resource failures are
99
+ masked without raw logging.
100
+
101
+ ### Better-auth origins — `./better-auth`
102
+
103
+ Fail-closed trusted-origin parsing and HTTPS cookie attributes. Does **not** import
104
+ `better-auth` — the host still calls `betterAuth({...})`.
105
+
106
+ ```ts
107
+ import {
108
+ cookieSettingsForSite,
109
+ trustedOriginsFromCsv,
110
+ } from "@vllnt/convex-helpers/better-auth";
111
+
112
+ const trustedOrigins = trustedOriginsFromCsv(
113
+ process.env.CONVEX_SITE_URL ?? "http://localhost:3211",
114
+ process.env.BETTER_AUTH_TRUSTED_ORIGINS ?? "",
115
+ );
116
+ ```
117
+
118
+ `parseTrustedOrigin` rejects credentials, paths, query/hash, controls and arbitrary
119
+ wildcards. HTTP is allowed only for exact localhost/127.0.0.1 origins. An explicit
120
+ `previewPattern` permits only HTTPS patterns shaped like
121
+ `https://app-*-org.example.com` (one wildcard inside a prefixed/suffixed first label).
122
+ The host must trust **every** deployment matched by its auth provider's wildcard
123
+ semantics; syntax validation does not establish deployment ownership.
124
+ Invalid extras are discarded; if none survive, the list helpers return `undefined`,
125
+ so the host omits `trustedOrigins` and retains Better Auth defaults. They do not
126
+ validate the host's entire auth configuration. `cookieSettingsForSite(siteUrl)`
127
+ returns secure, HttpOnly, SameSite=None attributes for a valid HTTPS origin only;
128
+ it does not replace CSRF/origin protection and is for hosts requiring cross-site cookies.
129
+
130
+ ### Identity — `./identity`
131
+
132
+ Map `ctx.auth.getUserIdentity()` to a host row. The host must configure a trusted
133
+ single issuer (subjects are issuer-local). To enforce it locally, pass
134
+ `{ auth: ctx.auth, trustedIssuer: "https://your-trusted-issuer.example" }` as the
135
+ helper context. A mismatched or missing issuer rejects before lookup/insert. The
136
+ issuer must be host configuration, never a request argument. Omitting it preserves
137
+ compatibility and relies on the host's single-issuer auth configuration.
138
+ Provide callbacks bound to the same
139
+ Convex mutation transaction, including the indexed lookup, for race-safe bootstrap.
140
+ Every competing creator must use the same lookup. Actions or remote callbacks are
141
+ not race-safe. `isAnonymous` reflects only a literal `true` JWT claim; missing/false
142
+ claims do not prove a verified or identified account.
143
+
144
+ `retargetRows(rows, apply)` processes at most 1000 rows sequentially, counts
145
+ `patched`/`deleted`/`skipped`, and propagates failures without starting later rows.
146
+ The host supplies bounded pages, authorization for both identities, conflict rules,
147
+ continuation, transaction/rollback and logging. It is **not** a secure account merge.
148
+ These helpers neither construct `betterAuth` nor install its anonymous plugin.
149
+
150
+ ```ts
151
+ import { getOrCreateFromAuth, retargetRows } from "@vllnt/convex-helpers/identity";
152
+
153
+ const user = await getOrCreateFromAuth(ctx, {
154
+ lookup: (subject) =>
155
+ ctx.db.query("users").withIndex("by_betterauth_id", (q) =>
156
+ q.eq("betterAuthId", subject),
157
+ ).unique(),
158
+ insert: async (subject) => {
159
+ const id = await ctx.db.insert("users", { betterAuthId: subject });
160
+ return (await ctx.db.get(id))!;
161
+ },
162
+ });
163
+ ```
164
+
92
165
  Migrating from `@vllnt/convex-mcp`? Change the import to `@vllnt/convex-helpers/mcp` — the API is
93
166
  identical. See [docs/API.md](./docs/API.md#mcp-tools-mcp) for the full `./mcp` reference.
94
167
 
@@ -104,7 +177,11 @@ pnpm test:coverage # must reach 100%
104
177
  ```
105
178
 
106
179
  Tests use [vitest](https://vitest.dev) with the node environment. 100% coverage is enforced via
107
- `vitest.config.mts` thresholds.
180
+ `vitest.config.mts` thresholds. Identity integration tests use **convex-test mocks**;
181
+ they do not prove real Better Auth sessions or anonymous plugin integration.
182
+ The separate `scripts/local-identity-proof.mjs` passed 16 concurrent real local-backend
183
+ bootstrap mutations with an injected synthetic identity (one row); this tests indexed
184
+ OCC, not login. Consuming apps must verify their actual auth integration independently.
108
185
 
109
186
  ## Contributing
110
187
 
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Fail-closed origin and cookie helpers for `@convex-dev/better-auth`.
3
+ * Does not import better-auth — the host still constructs `betterAuth(...)`.
4
+ */
5
+ export type CookieSettings = {
6
+ advanced: {
7
+ defaultCookieAttributes: {
8
+ httpOnly: true;
9
+ sameSite: "none";
10
+ secure: true;
11
+ };
12
+ useSecureCookies: true;
13
+ };
14
+ };
15
+ /**
16
+ * Parse one origin. HTTPS, no credentials/path/query/hash, no wildcards
17
+ * except an exact `previewPattern` (e.g. `https://app-*-org.vercel.app`).
18
+ */
19
+ export declare function parseTrustedOrigin(origin: string, options?: {
20
+ previewPattern?: string;
21
+ }): string | undefined;
22
+ /**
23
+ * Merge `siteUrl` with extra origins. Returns `undefined` when nothing extra
24
+ * survived parsing — the host then omits `trustedOrigins`.
25
+ * `siteUrl` uses the same rules (localhost HTTP allowed).
26
+ */
27
+ export declare function trustedOriginsFromList(siteUrl: string, extra: readonly string[], options?: {
28
+ previewPattern?: string;
29
+ }): string[] | undefined;
30
+ export declare function trustedOriginsFromCsv(siteUrl: string, csv: string, options?: {
31
+ previewPattern?: string;
32
+ }): string[] | undefined;
33
+ /** Secure cross-site cookies when `siteUrl` is HTTPS; otherwise `undefined`. */
34
+ export declare function cookieSettingsForSite(siteUrl: string): CookieSettings | undefined;
35
+ //# sourceMappingURL=better-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"better-auth.d.ts","sourceRoot":"","sources":["../src/better-auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE;QACR,uBAAuB,EAAE;YACvB,QAAQ,EAAE,IAAI,CAAC;YACf,QAAQ,EAAE,MAAM,CAAC;YACjB,MAAM,EAAE,IAAI,CAAC;SACd,CAAC;QACF,gBAAgB,EAAE,IAAI,CAAC;KACxB,CAAC;CACH,CAAC;AAwDF;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,MAAM,GAAG,SAAS,CAwBpB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,MAAM,EAAE,GAAG,SAAS,CAUtB;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,MAAM,EAAE,GAAG,SAAS,CAEtB;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GACd,cAAc,GAAG,SAAS,CAiB5B"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Fail-closed origin and cookie helpers for `@convex-dev/better-auth`.
3
+ * Does not import better-auth — the host still constructs `betterAuth(...)`.
4
+ */
5
+ function hasControlCharacter(value) {
6
+ // Control-character detection is the point of this helper.
7
+ // eslint-disable-next-line no-control-regex -- C0 + DEL
8
+ return /[\u0000-\u001F\u007F]/u.test(value);
9
+ }
10
+ function isLocalHttp(origin) {
11
+ return (origin.startsWith("http://localhost") ||
12
+ origin.startsWith("http://127.0.0.1"));
13
+ }
14
+ function httpsOrigin(trimmed) {
15
+ try {
16
+ const url = new URL(trimmed);
17
+ if (url.protocol !== "https:" ||
18
+ url.username !== "" ||
19
+ url.password !== "" ||
20
+ url.pathname !== "/" ||
21
+ url.search !== "" ||
22
+ url.hash !== "" ||
23
+ (trimmed !== url.origin && trimmed !== `${url.origin}/`)) {
24
+ return undefined;
25
+ }
26
+ return url.origin;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ function localHttpOrigin(trimmed) {
33
+ try {
34
+ const url = new URL(trimmed);
35
+ if (url.protocol !== "http:" ||
36
+ url.username !== "" ||
37
+ url.password !== "" ||
38
+ (url.hostname !== "localhost" && url.hostname !== "127.0.0.1") ||
39
+ url.pathname !== "/" ||
40
+ url.search !== "" ||
41
+ url.hash !== "" ||
42
+ (trimmed !== url.origin && trimmed !== `${url.origin}/`)) {
43
+ return undefined;
44
+ }
45
+ return url.origin;
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ }
51
+ /**
52
+ * Parse one origin. HTTPS, no credentials/path/query/hash, no wildcards
53
+ * except an exact `previewPattern` (e.g. `https://app-*-org.vercel.app`).
54
+ */
55
+ export function parseTrustedOrigin(origin, options = {}) {
56
+ const trimmed = origin.trim();
57
+ if (trimmed === "")
58
+ return undefined;
59
+ if (hasControlCharacter(origin))
60
+ return undefined;
61
+ if (options.previewPattern !== undefined &&
62
+ trimmed === options.previewPattern &&
63
+ /^https:\/\/[a-z0-9]+(?:-[a-z0-9]+)*-\*-[a-z0-9]+(?:-[a-z0-9]+)*(?:\.[a-z0-9]+(?:-[a-z0-9]+)*){2,}$/u.test(trimmed)) {
64
+ return trimmed;
65
+ }
66
+ if (isLocalHttp(trimmed)) {
67
+ return localHttpOrigin(trimmed);
68
+ }
69
+ if (!trimmed.startsWith("https://") ||
70
+ trimmed.includes("*") ||
71
+ trimmed.includes("?")) {
72
+ return undefined;
73
+ }
74
+ return httpsOrigin(trimmed);
75
+ }
76
+ /**
77
+ * Merge `siteUrl` with extra origins. Returns `undefined` when nothing extra
78
+ * survived parsing — the host then omits `trustedOrigins`.
79
+ * `siteUrl` uses the same rules (localhost HTTP allowed).
80
+ */
81
+ export function trustedOriginsFromList(siteUrl, extra, options = {}) {
82
+ const parsed = extra
83
+ .map((origin) => parseTrustedOrigin(origin, options))
84
+ .filter((origin) => origin !== undefined);
85
+ if (parsed.length === 0)
86
+ return undefined;
87
+ const site = parseTrustedOrigin(siteUrl, options);
88
+ if (site === undefined) {
89
+ return parsed;
90
+ }
91
+ return [site, ...parsed];
92
+ }
93
+ export function trustedOriginsFromCsv(siteUrl, csv, options = {}) {
94
+ return trustedOriginsFromList(siteUrl, csv.split(","), options);
95
+ }
96
+ /** Secure cross-site cookies when `siteUrl` is HTTPS; otherwise `undefined`. */
97
+ export function cookieSettingsForSite(siteUrl) {
98
+ if (!siteUrl.startsWith("https://") ||
99
+ parseTrustedOrigin(siteUrl) === undefined) {
100
+ return undefined;
101
+ }
102
+ return {
103
+ advanced: {
104
+ defaultCookieAttributes: {
105
+ httpOnly: true,
106
+ sameSite: "none",
107
+ secure: true,
108
+ },
109
+ useSecureCookies: true,
110
+ },
111
+ };
112
+ }
113
+ //# sourceMappingURL=better-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"better-auth.js","sourceRoot":"","sources":["../src/better-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Host-ctx identity glue: map `ctx.auth` to a host row, and retarget rows when
3
+ * an anonymous subject upgrades. Writes the **host's** tables — not a component.
4
+ */
5
+ export type AuthIdentity = {
6
+ isAnonymous?: unknown;
7
+ issuer?: string;
8
+ subject: string;
9
+ };
10
+ export type AuthCtx = {
11
+ auth: {
12
+ getUserIdentity: () => Promise<AuthIdentity | null>;
13
+ };
14
+ /** Host-configured issuer; never accept this value from request arguments. */
15
+ trustedIssuer?: string;
16
+ };
17
+ export declare function isAnonymousIdentity(identity: AuthIdentity): boolean;
18
+ export declare function requireIdentity(ctx: AuthCtx): Promise<AuthIdentity>;
19
+ /**
20
+ * Query-side lookup. Returns null when unauthenticated or the host row is not
21
+ * there yet (BetterAuth `onCreate` can lag a fresh anonymous session).
22
+ */
23
+ export declare function getFromAuth<T extends object>(ctx: AuthCtx, lookup: (subject: string) => Promise<null | T>): Promise<null | (T & {
24
+ isAnonymous: boolean;
25
+ })>;
26
+ export declare function requireFromAuth<T extends object>(ctx: AuthCtx, lookup: (subject: string) => Promise<null | T>): Promise<T & {
27
+ isAnonymous: boolean;
28
+ }>;
29
+ /**
30
+ * Mutation-side: look up the host row for the current identity, or insert it.
31
+ * Atomic when both callbacks use the SAME Convex mutation transaction,
32
+ * including an indexed lookup; every competing writer must use that lookup.
33
+ * Actions, remote calls and nontransactional callbacks do not provide uniqueness.
34
+ */
35
+ export declare function getOrCreateFromAuth<T extends object>(ctx: AuthCtx, handlers: {
36
+ insert: (subject: string) => Promise<T>;
37
+ lookup: (subject: string) => Promise<null | T>;
38
+ }): Promise<T & {
39
+ isAnonymous: boolean;
40
+ }>;
41
+ /**
42
+ * Process at most 1000 rows sequentially (one callback in flight).
43
+ * The host owns authorization, page size, continuation, conflict resolution and
44
+ * transaction boundaries. This is not an account merge or ownership check.
45
+ */
46
+ export declare function retargetRows<T>(rows: readonly T[], apply: (row: T) => Promise<"deleted" | "patched" | "skipped">): Promise<{
47
+ deleted: number;
48
+ patched: number;
49
+ skipped: number;
50
+ }>;
51
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE;QACJ,eAAe,EAAE,MAAM,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;KACrD,CAAC;IACF,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAEnE;AAcD,wBAAsB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAUzE;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,CAAC,SAAS,MAAM,EAChD,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,GAC7C,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG;IAAE,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,CAOhD;AAED,wBAAsB,eAAe,CAAC,CAAC,SAAS,MAAM,EACpD,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,GAC7C,OAAO,CAAC,CAAC,GAAG;IAAE,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CASvC;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EACxD,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE;IACR,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;CAChD,GACA,OAAO,CAAC,CAAC,GAAG;IAAE,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAKvC;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,GAC5D,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAchE"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Host-ctx identity glue: map `ctx.auth` to a host row, and retarget rows when
3
+ * an anonymous subject upgrades. Writes the **host's** tables — not a component.
4
+ */
5
+ import { ConvexError } from "convex/values";
6
+ export function isAnonymousIdentity(identity) {
7
+ return identity.isAnonymous === true;
8
+ }
9
+ function assertTrustedIssuer(ctx, identity) {
10
+ if (ctx.trustedIssuer !== undefined &&
11
+ identity.issuer !== ctx.trustedIssuer) {
12
+ throw new ConvexError({
13
+ code: "UNAUTHENTICATED",
14
+ message: "Untrusted issuer.",
15
+ });
16
+ }
17
+ }
18
+ export async function requireIdentity(ctx) {
19
+ const identity = await ctx.auth.getUserIdentity();
20
+ if (identity === null) {
21
+ throw new ConvexError({
22
+ code: "UNAUTHENTICATED",
23
+ message: "Sign-in required.",
24
+ });
25
+ }
26
+ assertTrustedIssuer(ctx, identity);
27
+ return identity;
28
+ }
29
+ /**
30
+ * Query-side lookup. Returns null when unauthenticated or the host row is not
31
+ * there yet (BetterAuth `onCreate` can lag a fresh anonymous session).
32
+ */
33
+ export async function getFromAuth(ctx, lookup) {
34
+ const identity = await ctx.auth.getUserIdentity();
35
+ if (identity === null)
36
+ return null;
37
+ assertTrustedIssuer(ctx, identity);
38
+ const row = await lookup(identity.subject);
39
+ if (row === null)
40
+ return null;
41
+ return { ...row, isAnonymous: isAnonymousIdentity(identity) };
42
+ }
43
+ export async function requireFromAuth(ctx, lookup) {
44
+ const user = await getFromAuth(ctx, lookup);
45
+ if (user === null) {
46
+ throw new ConvexError({
47
+ code: "USER_NOT_INITIALIZED",
48
+ message: "User not initialized — the auth session may still be settling.",
49
+ });
50
+ }
51
+ return user;
52
+ }
53
+ /**
54
+ * Mutation-side: look up the host row for the current identity, or insert it.
55
+ * Atomic when both callbacks use the SAME Convex mutation transaction,
56
+ * including an indexed lookup; every competing writer must use that lookup.
57
+ * Actions, remote calls and nontransactional callbacks do not provide uniqueness.
58
+ */
59
+ export async function getOrCreateFromAuth(ctx, handlers) {
60
+ const identity = await requireIdentity(ctx);
61
+ const existing = await handlers.lookup(identity.subject);
62
+ const row = existing ?? (await handlers.insert(identity.subject));
63
+ return { ...row, isAnonymous: isAnonymousIdentity(identity) };
64
+ }
65
+ /**
66
+ * Process at most 1000 rows sequentially (one callback in flight).
67
+ * The host owns authorization, page size, continuation, conflict resolution and
68
+ * transaction boundaries. This is not an account merge or ownership check.
69
+ */
70
+ export async function retargetRows(rows, apply) {
71
+ if (rows.length > 1000) {
72
+ throw new RangeError("retargetRows accepts at most 1000 rows per page");
73
+ }
74
+ return rows.reduce(async (previous, row) => {
75
+ const counts = await previous;
76
+ const outcome = await apply(row);
77
+ return { ...counts, [outcome]: counts[outcome] + 1 };
78
+ }, Promise.resolve({ deleted: 0, patched: 0, skipped: 0 }));
79
+ }
80
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/mcp/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CASlE;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,UAAU,GACf,OAAO,CACN;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GACrD;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CACvC,CAiCA"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/mcp/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CASlE;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,UAAU,GACf,OAAO,CACN;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GACrD;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CACvC,CA+CA"}
package/dist/mcp/auth.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { policyCallback } from "./callback.js";
1
2
  export function extractApiKey(request) {
2
3
  const authHeader = request.headers.get("authorization");
3
4
  if (!authHeader)
@@ -17,20 +18,32 @@ export async function validateRequest(request, auth) {
17
18
  valid: false,
18
19
  };
19
20
  }
20
- const isValid = await auth.validate(apiKey);
21
- if (!isValid) {
21
+ try {
22
+ const isValid = await policyCallback(() => auth.validate(apiKey));
23
+ if (!isValid) {
24
+ return {
25
+ response: new Response(JSON.stringify({ error: "Invalid API key." }), {
26
+ headers: { "Content-Type": "application/json" },
27
+ status: 401,
28
+ }),
29
+ valid: false,
30
+ };
31
+ }
32
+ let convexToken;
33
+ if (auth.convexToken) {
34
+ const resolveToken = auth.convexToken;
35
+ convexToken = await policyCallback(() => resolveToken(apiKey));
36
+ }
37
+ return { apiKey, convexToken, valid: true };
38
+ }
39
+ catch {
22
40
  return {
23
- response: new Response(JSON.stringify({ error: "Invalid API key." }), {
41
+ response: new Response(JSON.stringify({ error: "Authentication failed." }), {
24
42
  headers: { "Content-Type": "application/json" },
25
43
  status: 401,
26
44
  }),
27
45
  valid: false,
28
46
  };
29
47
  }
30
- let convexToken;
31
- if (auth.convexToken) {
32
- convexToken = await auth.convexToken(apiKey);
33
- }
34
- return { apiKey, convexToken, valid: true };
35
48
  }
36
49
  //# sourceMappingURL=auth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/mcp/auth.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACxD,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAElC,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAgB,EAChB,IAAgB;IAKhB,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAEtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,QAAQ,EAAE,IAAI,QAAQ,CACpB,IAAI,CAAC,SAAS,CAAC;gBACb,KAAK,EACH,gEAAgE;aACnE,CAAC,EACF,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CACjE;YACD,KAAK,EAAE,KAAK;SACb,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,QAAQ,EAAE,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,EAAE;gBACpE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,MAAM,EAAE,GAAG;aACZ,CAAC;YACF,KAAK,EAAE,KAAK;SACb,CAAC;IACJ,CAAC;IAED,IAAI,WAA+B,CAAC;IACpC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,WAAW,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/mcp/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACxD,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAElC,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAgB,EAChB,IAAgB;IAKhB,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAEtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,QAAQ,EAAE,IAAI,QAAQ,CACpB,IAAI,CAAC,SAAS,CAAC;gBACb,KAAK,EACH,gEAAgE;aACnE,CAAC,EACF,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CACjE;YACD,KAAK,EAAE,KAAK;SACb,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,QAAQ,EAAE,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,EAAE;oBACpE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;oBAC/C,MAAM,EAAE,GAAG;iBACZ,CAAC;gBACF,KAAK,EAAE,KAAK;aACb,CAAC;QACJ,CAAC;QAED,IAAI,WAA+B,CAAC;QACpC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC;YACtC,WAAW,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QACjE,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,QAAQ,EAAE,IAAI,QAAQ,CACpB,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,EACnD;gBACE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,MAAM,EAAE,GAAG;aACZ,CACF;YACD,KAAK,EAAE,KAAK;SACb,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Bound host policy callbacks; timeout does not cancel host-owned work. */
2
+ export declare function policyCallback<T>(callback: () => Promise<T> | T): Promise<T>;
3
+ //# sourceMappingURL=callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/mcp/callback.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,wBAAsB,cAAc,CAAC,CAAC,EACpC,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC,CAcZ"}
@@ -0,0 +1,18 @@
1
+ /** Bound host policy callbacks; timeout does not cancel host-owned work. */
2
+ export async function policyCallback(callback) {
3
+ let handle;
4
+ try {
5
+ return await Promise.race([
6
+ Promise.resolve().then(callback),
7
+ new Promise((_, reject) => {
8
+ handle = setTimeout(() => {
9
+ reject(new Error("Policy callback timed out"));
10
+ }, 10_000);
11
+ }),
12
+ ]);
13
+ }
14
+ finally {
15
+ clearTimeout(handle);
16
+ }
17
+ }
18
+ //# sourceMappingURL=callback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/mcp/callback.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAA8B;IAE9B,IAAI,MAAiD,CAAC;IACtD,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC;YACxB,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;YAChC,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;gBAC/B,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE;oBACvB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC;gBACjD,CAAC,EAAE,MAAM,CAAC,CAAC;YACb,CAAC,CAAC;SACH,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/mcp/resources/register.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAG3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,KAAK,gBAAgB,GAAG;IACtB,UAAU,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,GAAG,SAAS,CAAC;IAC3D,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACrC,gBAAgB,EAAE,CAUpB;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,gBAAgB,EAAE,GAC5B,IAAI,CA4CN"}
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/mcp/resources/register.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAG3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,KAAK,gBAAgB,GAAG;IACtB,UAAU,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,GAAG,SAAS,CAAC;IAC3D,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACrC,gBAAgB,EAAE,CAUpB;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,gBAAgB,EAAE,GAC5B,IAAI,CAuCN"}
@@ -31,13 +31,8 @@ export function registerResources(mcpServer, client, resources) {
31
31
  ],
32
32
  };
33
33
  }
34
- catch (error) {
35
- console.error("[convex-mcp] resource read failed", {
36
- error,
37
- resource: uriPattern,
38
- });
39
- // Mask the raw Convex error from the client (parity with the tool
40
- // handler); the real error is logged server-side above.
34
+ catch {
35
+ // Do not log raw exceptions: provider errors may contain secrets.
41
36
  throw new Error("Resource read failed");
42
37
  }
43
38
  });
@@ -1 +1 @@
1
- {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../src/mcp/resources/register.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAE3E,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAYnD,MAAM,UAAU,gBAAgB,CAC9B,SAAsC;IAEtC,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;QACnE,UAAU,EAAE,WAAW,CAAC,IAAI;YAC1B,CAAC,CAAC,eAAe,CAAC,WAAW,CAAC,IAAI,CAAC;YACnC,CAAC,CAAC,SAAS;QACb,WAAW,EAAE,WAAW,CAAC,WAAW;QACpC,WAAW;QACX,QAAQ,EAAE,IAAI,gBAAgB,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAC/D,UAAU;KACX,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,SAAoB,EACpB,MAAoB,EACpB,SAA6B;IAE7B,KAAK,MAAM,EACT,UAAU,EACV,WAAW,EACX,WAAW,EACX,QAAQ,EACR,UAAU,GACX,IAAI,SAAS,EAAE,CAAC;QACf,SAAS,CAAC,QAAQ,CAChB,UAAU,EACV,QAAQ,EACR;YACE,WAAW;YACX,QAAQ,EAAE,kBAAkB;SAC7B,EACD,KAAK,EAAE,GAAG,EAAE,UAAmC,EAAE,EAAE;YACjD,IAAI,CAAC;gBACH,MAAM,mBAAmB,GACvB,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;gBAC9C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,WAAW,CAAC,GAAG,EACf,mBAAmB,CACpB,CAAC;gBACF,OAAO;oBACL,QAAQ,EAAE;wBACR;4BACE,QAAQ,EAAE,kBAAkB;4BAC5B,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC;4BAC7B,GAAG,EAAE,GAAG,CAAC,IAAI;yBACd;qBACF;iBACF,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE;oBACjD,KAAK;oBACL,QAAQ,EAAE,UAAU;iBACrB,CAAC,CAAC;gBACH,kEAAkE;gBAClE,wDAAwD;gBACxD,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../src/mcp/resources/register.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAE3E,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAYnD,MAAM,UAAU,gBAAgB,CAC9B,SAAsC;IAEtC,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;QACnE,UAAU,EAAE,WAAW,CAAC,IAAI;YAC1B,CAAC,CAAC,eAAe,CAAC,WAAW,CAAC,IAAI,CAAC;YACnC,CAAC,CAAC,SAAS;QACb,WAAW,EAAE,WAAW,CAAC,WAAW;QACpC,WAAW;QACX,QAAQ,EAAE,IAAI,gBAAgB,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAC/D,UAAU;KACX,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,SAAoB,EACpB,MAAoB,EACpB,SAA6B;IAE7B,KAAK,MAAM,EACT,UAAU,EACV,WAAW,EACX,WAAW,EACX,QAAQ,EACR,UAAU,GACX,IAAI,SAAS,EAAE,CAAC;QACf,SAAS,CAAC,QAAQ,CAChB,UAAU,EACV,QAAQ,EACR;YACE,WAAW;YACX,QAAQ,EAAE,kBAAkB;SAC7B,EACD,KAAK,EAAE,GAAG,EAAE,UAAmC,EAAE,EAAE;YACjD,IAAI,CAAC;gBACH,MAAM,mBAAmB,GACvB,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;gBAC9C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,WAAW,CAAC,GAAG,EACf,mBAAmB,CACpB,CAAC;gBACF,OAAO;oBACL,QAAQ,EAAE;wBACR;4BACE,QAAQ,EAAE,kBAAkB;4BAC5B,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC;4BAC7B,GAAG,EAAE,GAAG,CAAC,IAAI;yBACd;qBACF;iBACF,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,kEAAkE;gBAClE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAgB,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAuB9E,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,eAAe,CAwKrE"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAgB,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAuB9E,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,eAAe,CAyJrE"}
@@ -5,7 +5,7 @@ import { validateRequest } from "./auth.js";
5
5
  import { createPaginationContext } from "./pagination/context.js";
6
6
  import { getOriginalToolsList, registerPaginationHandlers, registerTwoPhaseHandlers, } from "./pagination/handlers.js";
7
7
  import { prepareResources, registerResources } from "./resources/register.js";
8
- import { findToolsWithReservedArgs as findToolsWithReservedArguments, prepareTools, registerTools, } from "./tools/register.js";
8
+ import { prepareTools, registerTools } from "./tools/register.js";
9
9
  function createDefaultClient(convexUrl, convexToken) {
10
10
  const client = new ConvexHttpClient(convexUrl);
11
11
  if (convexToken) {
@@ -49,18 +49,6 @@ export function createMCPServer(config) {
49
49
  const prepared = prepareTools(config.tools ?? {});
50
50
  const preparedRes = prepareResources(config.resources ?? {});
51
51
  const paginationCtx = createPaginationContext(config.pagination);
52
- if (!hooks?.onToolCall) {
53
- const stripped = findToolsWithReservedArguments(config.tools ?? {});
54
- if (stripped.size > 0) {
55
- const summary = [...stripped.entries()]
56
- .map(([tool, keys]) => `${tool} (${keys.join(", ")})`)
57
- .join("; ");
58
- console.warn(`[convex-mcp] tools declare reserved \`_*\` args but no onToolCall hook is configured. ` +
59
- `These args are stripped from the published schema and will never be injected, ` +
60
- `so every dispatched call will fail Convex validation. ` +
61
- `Configure hooks.onToolCall to inject them via extendArgs. Affected tools: ${summary}`);
62
- }
63
- }
64
52
  function createServerAndTransport(requestId, convexToken, apiKey) {
65
53
  const mcpServer = new McpServer({
66
54
  name: serverName,