@vllnt/convex-helpers 0.1.0-canary.1e0f54a
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/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/auth.d.ts +11 -0
- package/dist/mcp/auth.d.ts.map +1 -0
- package/dist/mcp/auth.js +36 -0
- package/dist/mcp/auth.js.map +1 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +5 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/pagination/context.d.ts +9 -0
- package/dist/mcp/pagination/context.d.ts.map +1 -0
- package/dist/mcp/pagination/context.js +21 -0
- package/dist/mcp/pagination/context.js.map +1 -0
- package/dist/mcp/pagination/cursor.d.ts +7 -0
- package/dist/mcp/pagination/cursor.d.ts.map +1 -0
- package/dist/mcp/pagination/cursor.js +60 -0
- package/dist/mcp/pagination/cursor.js.map +1 -0
- package/dist/mcp/pagination/handlers.d.ts +12 -0
- package/dist/mcp/pagination/handlers.d.ts.map +1 -0
- package/dist/mcp/pagination/handlers.js +91 -0
- package/dist/mcp/pagination/handlers.js.map +1 -0
- package/dist/mcp/pagination/types.d.ts +26 -0
- package/dist/mcp/pagination/types.d.ts.map +1 -0
- package/dist/mcp/pagination/types.js +2 -0
- package/dist/mcp/pagination/types.js.map +1 -0
- package/dist/mcp/resources/helpers.d.ts +9 -0
- package/dist/mcp/resources/helpers.d.ts.map +1 -0
- package/dist/mcp/resources/helpers.js +8 -0
- package/dist/mcp/resources/helpers.js.map +1 -0
- package/dist/mcp/resources/register.d.ts +14 -0
- package/dist/mcp/resources/register.d.ts.map +1 -0
- package/dist/mcp/resources/register.js +40 -0
- package/dist/mcp/resources/register.js.map +1 -0
- package/dist/mcp/resources/types.d.ts +7 -0
- package/dist/mcp/resources/types.d.ts.map +1 -0
- package/dist/mcp/resources/types.js +2 -0
- package/dist/mcp/resources/types.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +139 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/helpers.d.ts +16 -0
- package/dist/mcp/tools/helpers.d.ts.map +1 -0
- package/dist/mcp/tools/helpers.js +34 -0
- package/dist/mcp/tools/helpers.js.map +1 -0
- package/dist/mcp/tools/register.d.ts +23 -0
- package/dist/mcp/tools/register.d.ts.map +1 -0
- package/dist/mcp/tools/register.js +195 -0
- package/dist/mcp/tools/register.js.map +1 -0
- package/dist/mcp/tools/types.d.ts +71 -0
- package/dist/mcp/tools/types.d.ts.map +1 -0
- package/dist/mcp/tools/types.js +2 -0
- package/dist/mcp/tools/types.js.map +1 -0
- package/dist/mcp/types.d.ts +50 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/mcp/validators.d.ts +8 -0
- package/dist/mcp/validators.d.ts.map +1 -0
- package/dist/mcp/validators.js +135 -0
- package/dist/mcp/validators.js.map +1 -0
- package/package.json +95 -0
- package/src/index.test.ts +164 -0
- package/src/index.ts +62 -0
- package/src/mcp/auth.ts +53 -0
- package/src/mcp/index.ts +23 -0
- package/src/mcp/pagination/context.ts +27 -0
- package/src/mcp/pagination/cursor.ts +99 -0
- package/src/mcp/pagination/handlers.ts +172 -0
- package/src/mcp/pagination/types.ts +27 -0
- package/src/mcp/resources/helpers.ts +19 -0
- package/src/mcp/resources/register.ts +63 -0
- package/src/mcp/resources/types.ts +7 -0
- package/src/mcp/server.ts +209 -0
- package/src/mcp/tools/helpers.ts +47 -0
- package/src/mcp/tools/register.ts +256 -0
- package/src/mcp/tools/types.ts +74 -0
- package/src/mcp/types.ts +65 -0
- package/src/mcp/validators.ts +175 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type ConvexValidator = {
|
|
2
|
+
element?: ConvexValidator;
|
|
3
|
+
fields?: Record<string, ConvexValidator>;
|
|
4
|
+
isOptional: "optional" | "required";
|
|
5
|
+
key?: ConvexValidator;
|
|
6
|
+
kind: string;
|
|
7
|
+
members?: ConvexValidator[];
|
|
8
|
+
tableName?: string;
|
|
9
|
+
value?: unknown;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Injectable Convex client interface. Compatible with both ConvexHttpClient
|
|
13
|
+
* (production) and convex-test's `t` (testing).
|
|
14
|
+
*
|
|
15
|
+
* Uses `any` for functionRef because Convex's FunctionReference is a complex
|
|
16
|
+
* generic that cannot be expressed without importing convex internals.
|
|
17
|
+
*/
|
|
18
|
+
export type ConvexClient = {
|
|
19
|
+
action(functionRef: any, ...arguments_: any[]): Promise<any>;
|
|
20
|
+
mutation(functionRef: any, ...arguments_: any[]): Promise<any>;
|
|
21
|
+
query(functionRef: any, ...arguments_: any[]): Promise<any>;
|
|
22
|
+
};
|
|
23
|
+
export type AuthConfig = {
|
|
24
|
+
convexToken?: (apiKey: string) => Promise<string | undefined> | string | undefined;
|
|
25
|
+
validate: (apiKey: string) => boolean | Promise<boolean>;
|
|
26
|
+
};
|
|
27
|
+
import type { PaginationConfig } from "./pagination/types.js";
|
|
28
|
+
import type { ResourceDef } from "./resources/types.js";
|
|
29
|
+
import type { LifecycleHooks, ToolDef } from "./tools/types.js";
|
|
30
|
+
export type { PaginationConfig, ResourceDef, ToolDef };
|
|
31
|
+
export type ServerConfig = {
|
|
32
|
+
auth: AuthConfig;
|
|
33
|
+
client?: ConvexClient;
|
|
34
|
+
convexUrl?: string;
|
|
35
|
+
hooks?: LifecycleHooks;
|
|
36
|
+
name?: string;
|
|
37
|
+
pagination?: PaginationConfig;
|
|
38
|
+
resources?: Record<string, ResourceDef>;
|
|
39
|
+
tools?: Record<string, ToolDef>;
|
|
40
|
+
version?: string;
|
|
41
|
+
};
|
|
42
|
+
export type ConvexMCPServer = {
|
|
43
|
+
handler: () => {
|
|
44
|
+
GET: (request: Request) => Promise<Response>;
|
|
45
|
+
POST: (request: Request) => Promise<Response>;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export { type ToolPage, type ToolSummary } from "./pagination/types.js";
|
|
49
|
+
export { type CallContext, type FunctionType, type LifecycleHooks, type OnCallResult, } from "./tools/types.js";
|
|
50
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAC;IACpC,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IAEzB,MAAM,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7D,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/D,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CAE7D,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,WAAW,CAAC,EAAE,CACZ,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC;IACtD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1D,CAAC;AAEF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAEvD,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM;QACb,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;KAC/C,CAAC;CACH,CAAC;AAEF,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ConvexValidator } from "./types.js";
|
|
3
|
+
export declare class UnsupportedValidatorError extends Error {
|
|
4
|
+
constructor(kind: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function convertValidator(validator: ConvexValidator): z.ZodTypeAny;
|
|
7
|
+
export declare function convexArgsToZod(argumentsValidator: ConvexValidator): z.ZodObject<Record<string, z.ZodTypeAny>>;
|
|
8
|
+
//# sourceMappingURL=validators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../src/mcp/validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,IAAI,EAAE,MAAM;CAMzB;AA6ID,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,eAAe,GAAG,CAAC,CAAC,UAAU,CAMzE;AAED,wBAAgB,eAAe,CAC7B,kBAAkB,EAAE,eAAe,GAClC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAY3C"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export class UnsupportedValidatorError extends Error {
|
|
3
|
+
constructor(kind) {
|
|
4
|
+
super(`Unsupported Convex validator kind: "${kind}". This may be from a newer version of Convex.`);
|
|
5
|
+
this.name = "UnsupportedValidatorError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function isConvexValidator(value) {
|
|
9
|
+
return (typeof value === "object" &&
|
|
10
|
+
value !== null &&
|
|
11
|
+
"kind" in value &&
|
|
12
|
+
"isOptional" in value);
|
|
13
|
+
}
|
|
14
|
+
function isLiteralValue(value) {
|
|
15
|
+
const t = typeof value;
|
|
16
|
+
return t === "string" || t === "number" || t === "boolean" || value === null;
|
|
17
|
+
}
|
|
18
|
+
function isAllStringLiteralUnion(members) {
|
|
19
|
+
return (members.length > 1 &&
|
|
20
|
+
members.every((m) => m.kind === "literal" && typeof m.value === "string"));
|
|
21
|
+
}
|
|
22
|
+
function hasAtLeastTwo(array) {
|
|
23
|
+
return array.length >= 2;
|
|
24
|
+
}
|
|
25
|
+
// Callers guarantee arr.length >= 2 (pre-checked by isAllStringLiteralUnion or length === 1 early return)
|
|
26
|
+
function asTuple(array) {
|
|
27
|
+
/* v8 ignore start -- callers guarantee length >= 2 */
|
|
28
|
+
if (!hasAtLeastTwo(array))
|
|
29
|
+
throw new Error("Expected at least 2 elements");
|
|
30
|
+
/* v8 ignore stop */
|
|
31
|
+
return array;
|
|
32
|
+
}
|
|
33
|
+
function convertKind(validator) {
|
|
34
|
+
switch (validator.kind) {
|
|
35
|
+
case "string":
|
|
36
|
+
return z.string();
|
|
37
|
+
case "float64":
|
|
38
|
+
return z.number();
|
|
39
|
+
case "boolean":
|
|
40
|
+
return z.boolean();
|
|
41
|
+
case "null":
|
|
42
|
+
return z.null();
|
|
43
|
+
case "int64":
|
|
44
|
+
return z
|
|
45
|
+
.string()
|
|
46
|
+
.describe("64-bit integer as string (BigInt — JSON cannot represent bigint)");
|
|
47
|
+
case "bytes":
|
|
48
|
+
return z.string().describe("Binary data as base64-encoded string");
|
|
49
|
+
case "id":
|
|
50
|
+
return z
|
|
51
|
+
.string()
|
|
52
|
+
.describe(validator.tableName
|
|
53
|
+
? `Convex document ID for table '${validator.tableName}'`
|
|
54
|
+
: "Convex document ID");
|
|
55
|
+
case "literal":
|
|
56
|
+
if (!isLiteralValue(validator.value)) {
|
|
57
|
+
throw new UnsupportedValidatorError("literal (unsupported value type)");
|
|
58
|
+
}
|
|
59
|
+
return z.literal(validator.value);
|
|
60
|
+
case "array":
|
|
61
|
+
if (!validator.element) {
|
|
62
|
+
throw new UnsupportedValidatorError("array (missing element)");
|
|
63
|
+
}
|
|
64
|
+
return z.array(convertValidator(validator.element));
|
|
65
|
+
case "object": {
|
|
66
|
+
if (!validator.fields) {
|
|
67
|
+
return z.object({});
|
|
68
|
+
}
|
|
69
|
+
const shape = {};
|
|
70
|
+
for (const [key, field] of Object.entries(validator.fields)) {
|
|
71
|
+
shape[key] = convertValidator(field);
|
|
72
|
+
}
|
|
73
|
+
return z.object(shape);
|
|
74
|
+
}
|
|
75
|
+
case "union": {
|
|
76
|
+
if (!validator.members || validator.members.length === 0) {
|
|
77
|
+
throw new UnsupportedValidatorError("union (missing members)");
|
|
78
|
+
}
|
|
79
|
+
if (isAllStringLiteralUnion(validator.members)) {
|
|
80
|
+
const values = validator.members.map((m) => m.value);
|
|
81
|
+
return z.enum(asTuple(values));
|
|
82
|
+
}
|
|
83
|
+
const converted = validator.members.map((m) => convertValidator(m));
|
|
84
|
+
if (converted.length === 1) {
|
|
85
|
+
const [single] = converted;
|
|
86
|
+
/* v8 ignore start -- impossible after converted.length === 1; needed for noUncheckedIndexedAccess */
|
|
87
|
+
if (!single)
|
|
88
|
+
throw new UnsupportedValidatorError("union (missing member)");
|
|
89
|
+
/* v8 ignore stop */
|
|
90
|
+
return single;
|
|
91
|
+
}
|
|
92
|
+
return z.union(asTuple(converted));
|
|
93
|
+
}
|
|
94
|
+
case "record": {
|
|
95
|
+
if (!validator.key || !validator.value) {
|
|
96
|
+
throw new UnsupportedValidatorError("record (missing key or value)");
|
|
97
|
+
}
|
|
98
|
+
const keyKind = validator.key.kind;
|
|
99
|
+
if (keyKind !== "string" && keyKind !== "id") {
|
|
100
|
+
throw new UnsupportedValidatorError(`record key must be string or id, got "${keyKind}"`);
|
|
101
|
+
}
|
|
102
|
+
const valueValidator = validator.value;
|
|
103
|
+
if (!isConvexValidator(valueValidator)) {
|
|
104
|
+
throw new UnsupportedValidatorError("record (invalid value validator)");
|
|
105
|
+
}
|
|
106
|
+
// Key kind validated as "string" | "id" above — always maps to z.string()
|
|
107
|
+
return z.record(z.string(), convertValidator(valueValidator));
|
|
108
|
+
}
|
|
109
|
+
case "any":
|
|
110
|
+
return z.any();
|
|
111
|
+
default:
|
|
112
|
+
throw new UnsupportedValidatorError(validator.kind);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export function convertValidator(validator) {
|
|
116
|
+
const base = convertKind(validator);
|
|
117
|
+
if (validator.isOptional === "optional") {
|
|
118
|
+
return base.optional();
|
|
119
|
+
}
|
|
120
|
+
return base;
|
|
121
|
+
}
|
|
122
|
+
export function convexArgsToZod(argumentsValidator) {
|
|
123
|
+
if (argumentsValidator.kind !== "object") {
|
|
124
|
+
throw new Error("Convex function args must be a v.object() validator");
|
|
125
|
+
}
|
|
126
|
+
if (!argumentsValidator.fields) {
|
|
127
|
+
return z.object({});
|
|
128
|
+
}
|
|
129
|
+
const shape = {};
|
|
130
|
+
for (const [key, field] of Object.entries(argumentsValidator.fields)) {
|
|
131
|
+
shape[key] = convertValidator(field);
|
|
132
|
+
}
|
|
133
|
+
return z.object(shape);
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=validators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/mcp/validators.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vllnt/convex-helpers",
|
|
3
|
+
"version": "0.1.0-canary.1e0f54a",
|
|
4
|
+
"description": "Typed host-side helpers for Convex backends — builders, errors, validators, relationships, pagination, HTTP, env, and observability.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"packageManager": "pnpm@9.15.4",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"src"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
"./package.json": "./package.json",
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./mcp": {
|
|
19
|
+
"types": "./dist/mcp/index.d.ts",
|
|
20
|
+
"default": "./dist/mcp/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc --project ./tsconfig.build.json",
|
|
28
|
+
"typecheck": "tsc --noEmit && tsc --noEmit --project tsconfig.test.json",
|
|
29
|
+
"typecheck:ci": "tsc --noEmit --project tsconfig.ci.json && tsc --noEmit --project tsconfig.test.json",
|
|
30
|
+
"lint": "eslint .",
|
|
31
|
+
"test": "vitest run --passWithNoTests",
|
|
32
|
+
"test:watch": "vitest --typecheck --clearScreen false",
|
|
33
|
+
"test:coverage": "vitest run --coverage --coverage.reporter=text",
|
|
34
|
+
"generate:llms": "node scripts/generate-llms.mjs",
|
|
35
|
+
"preversion": "pnpm install --frozen-lockfile && pnpm build && pnpm test && pnpm typecheck && pnpm generate:llms"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
|
+
"convex": "^1.36.1",
|
|
40
|
+
"zod": "^4.3.6"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@modelcontextprotocol/sdk": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"zod": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
52
|
+
"@types/node": "^25.6.0",
|
|
53
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
54
|
+
"@vllnt/eslint-config": "^1.0.0",
|
|
55
|
+
"@vllnt/typescript": "^1.0.0",
|
|
56
|
+
"convex": "^1.36.1",
|
|
57
|
+
"convex-test": "^0.0.50",
|
|
58
|
+
"eslint": "^9.0.0",
|
|
59
|
+
"prettier": "^3.4.0",
|
|
60
|
+
"typescript": "^5.7.0",
|
|
61
|
+
"vitest": "^3.0.0",
|
|
62
|
+
"zod": "^4.3.6"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=18"
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/vllnt/convex-helpers#readme",
|
|
71
|
+
"bugs": {
|
|
72
|
+
"url": "https://github.com/vllnt/convex-helpers/issues"
|
|
73
|
+
},
|
|
74
|
+
"repository": {
|
|
75
|
+
"type": "git",
|
|
76
|
+
"url": "https://github.com/vllnt/convex-helpers"
|
|
77
|
+
},
|
|
78
|
+
"author": {
|
|
79
|
+
"name": "bntvllnt",
|
|
80
|
+
"url": "https://bntvllnt.com"
|
|
81
|
+
},
|
|
82
|
+
"funding": {
|
|
83
|
+
"type": "github",
|
|
84
|
+
"url": "https://github.com/sponsors/bntvllnt"
|
|
85
|
+
},
|
|
86
|
+
"keywords": [
|
|
87
|
+
"convex",
|
|
88
|
+
"convex-helpers",
|
|
89
|
+
"typescript",
|
|
90
|
+
"backend",
|
|
91
|
+
"utils",
|
|
92
|
+
"mcp",
|
|
93
|
+
"model-context-protocol"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { asyncMap, NullDocumentError, nullThrows, pruneNull } from "./index.js";
|
|
4
|
+
|
|
5
|
+
describe("asyncMap", () => {
|
|
6
|
+
it("returns empty array for empty input", async () => {
|
|
7
|
+
const result = await asyncMap([], (x: number) => Promise.resolve(x * 2));
|
|
8
|
+
expect(result).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("maps a single element", async () => {
|
|
12
|
+
const result = await asyncMap([1], (x) => Promise.resolve(x * 2));
|
|
13
|
+
expect(result).toEqual([2]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("maps multiple elements in order", async () => {
|
|
17
|
+
const result = await asyncMap([1, 2, 3], (x) => Promise.resolve(x * 2));
|
|
18
|
+
expect(result).toEqual([2, 4, 6]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("passes index to mapper", async () => {
|
|
22
|
+
const result = await asyncMap(
|
|
23
|
+
["a", "b", "c"],
|
|
24
|
+
async (item, index) => `${String(index)}:${item}`,
|
|
25
|
+
);
|
|
26
|
+
expect(result).toEqual(["0:a", "1:b", "2:c"]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("resolves all promises and returns results in order", async () => {
|
|
30
|
+
const result = await asyncMap([10, 20, 30], (x) => Promise.resolve(x * 3));
|
|
31
|
+
expect(result).toEqual([30, 60, 90]);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("pruneNull", () => {
|
|
36
|
+
it("returns empty array for empty input", () => {
|
|
37
|
+
expect(pruneNull([])).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("removes all nulls", () => {
|
|
41
|
+
const nullValue: null = null;
|
|
42
|
+
expect(pruneNull([nullValue, nullValue])).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("removes all undefineds", () => {
|
|
46
|
+
expect(pruneNull([undefined, undefined])).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("removes mixed null and undefined", () => {
|
|
50
|
+
const nullValue: null = null;
|
|
51
|
+
expect(pruneNull([nullValue, undefined, nullValue])).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("keeps all non-null values", () => {
|
|
55
|
+
expect(pruneNull([1, 2, 3])).toEqual([1, 2, 3]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("filters mixed array of values and nulls", () => {
|
|
59
|
+
const nullValue: null = null;
|
|
60
|
+
expect(pruneNull([1, nullValue, 2, undefined, 3])).toEqual([1, 2, 3]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("works with strings", () => {
|
|
64
|
+
const nullValue: null = null;
|
|
65
|
+
expect(pruneNull(["a", nullValue, "b"])).toEqual(["a", "b"]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("works with objects", () => {
|
|
69
|
+
const object = { id: 1 };
|
|
70
|
+
const nullValue: null = null;
|
|
71
|
+
expect(pruneNull([object, nullValue])).toEqual([object]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("preserves falsy non-null values (0, false, empty string)", () => {
|
|
75
|
+
const nullValue: null = null;
|
|
76
|
+
expect(pruneNull([0, false, "", nullValue, undefined])).toEqual([
|
|
77
|
+
0,
|
|
78
|
+
false,
|
|
79
|
+
"",
|
|
80
|
+
]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("NullDocumentError", () => {
|
|
85
|
+
it("is an instance of Error", () => {
|
|
86
|
+
const error = new NullDocumentError();
|
|
87
|
+
expect(error).toBeInstanceOf(Error);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("has name NullDocumentError", () => {
|
|
91
|
+
const error = new NullDocumentError();
|
|
92
|
+
expect(error.name).toBe("NullDocumentError");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("uses default message when none provided", () => {
|
|
96
|
+
const error = new NullDocumentError();
|
|
97
|
+
expect(error.message).toBe("Expected a non-null document");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("uses custom message when provided", () => {
|
|
101
|
+
const error = new NullDocumentError("custom message");
|
|
102
|
+
expect(error.message).toBe("custom message");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("nullThrows", () => {
|
|
107
|
+
it("returns the value when non-null", () => {
|
|
108
|
+
expect(nullThrows(42)).toBe(42);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("returns the value when truthy string", () => {
|
|
112
|
+
expect(nullThrows("hello")).toBe("hello");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("returns 0 (falsy but non-null)", () => {
|
|
116
|
+
expect(nullThrows(0)).toBe(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("returns false (falsy but non-null)", () => {
|
|
120
|
+
expect(nullThrows(false)).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("returns empty string (falsy but non-null)", () => {
|
|
124
|
+
expect(nullThrows("")).toBe("");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("returns objects", () => {
|
|
128
|
+
const object = { id: 1 };
|
|
129
|
+
expect(nullThrows(object)).toBe(object);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("throws NullDocumentError when value is null", () => {
|
|
133
|
+
const nullValue: null = null;
|
|
134
|
+
expect(() => {
|
|
135
|
+
nullThrows(nullValue);
|
|
136
|
+
}).toThrow(NullDocumentError);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("throws NullDocumentError when value is undefined", () => {
|
|
140
|
+
expect(() => {
|
|
141
|
+
nullThrows(undefined);
|
|
142
|
+
}).toThrow(NullDocumentError);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("throws with default message when no message supplied", () => {
|
|
146
|
+
const nullValue: null = null;
|
|
147
|
+
expect(() => {
|
|
148
|
+
nullThrows(nullValue);
|
|
149
|
+
}).toThrow("Expected a non-null document");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("throws with custom message when supplied", () => {
|
|
153
|
+
const nullValue: null = null;
|
|
154
|
+
expect(() => {
|
|
155
|
+
nullThrows(nullValue, "doc 123 not found");
|
|
156
|
+
}).toThrow("doc 123 not found");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("throws with custom message for undefined", () => {
|
|
160
|
+
expect(() => {
|
|
161
|
+
nullThrows(undefined, "missing doc");
|
|
162
|
+
}).toThrow("missing doc");
|
|
163
|
+
});
|
|
164
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @vllnt/convex-helpers
|
|
3
|
+
* Pure host-side helpers for Convex backends.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Applies an async function to every element of an array, returning results in order.
|
|
8
|
+
*
|
|
9
|
+
* @param list - Input array
|
|
10
|
+
* @param fn - Async mapper function
|
|
11
|
+
* @returns Promise resolving to mapped array in original order
|
|
12
|
+
* @example
|
|
13
|
+
* const docs = await asyncMap(ids, (id) => ctx.db.get(id));
|
|
14
|
+
*/
|
|
15
|
+
export async function asyncMap<T, TResult>(
|
|
16
|
+
list: T[],
|
|
17
|
+
fn: (item: T, index: number) => Promise<TResult>,
|
|
18
|
+
): Promise<TResult[]> {
|
|
19
|
+
return Promise.all(list.map(fn));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Filters null and undefined values from an array.
|
|
24
|
+
*
|
|
25
|
+
* @param list - Input array potentially containing null/undefined
|
|
26
|
+
* @returns Array with all null and undefined values removed
|
|
27
|
+
* @example
|
|
28
|
+
* const docs = pruneNull(await asyncMap(ids, (id) => ctx.db.get(id)));
|
|
29
|
+
*/
|
|
30
|
+
export function pruneNull<T>(list: (null | T | undefined)[]): T[] {
|
|
31
|
+
return list.filter((item): item is T => item !== null && item !== undefined);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Error thrown when a required document is null or undefined.
|
|
36
|
+
*/
|
|
37
|
+
export class NullDocumentError extends Error {
|
|
38
|
+
constructor(message = "Expected a non-null document") {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "NullDocumentError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Asserts a value is non-null and non-undefined, throwing NullDocumentError when absent.
|
|
46
|
+
*
|
|
47
|
+
* @param value - Value to check
|
|
48
|
+
* @param message - Optional custom error message
|
|
49
|
+
* @returns The value, narrowed to exclude null and undefined
|
|
50
|
+
* @throws {NullDocumentError} When value is null or undefined
|
|
51
|
+
* @example
|
|
52
|
+
* const doc = nullThrows(await ctx.db.get(id), `Document ${id} not found`);
|
|
53
|
+
*/
|
|
54
|
+
export function nullThrows<T>(
|
|
55
|
+
value: null | T | undefined,
|
|
56
|
+
message?: string,
|
|
57
|
+
): T {
|
|
58
|
+
if (value === null || value === undefined) {
|
|
59
|
+
throw new NullDocumentError(message);
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
package/src/mcp/auth.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AuthConfig } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function extractApiKey(request: Request): string | undefined {
|
|
4
|
+
const authHeader = request.headers.get("authorization");
|
|
5
|
+
if (!authHeader) return undefined;
|
|
6
|
+
|
|
7
|
+
if (authHeader.startsWith("Bearer ")) {
|
|
8
|
+
return authHeader.slice(7);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function validateRequest(
|
|
15
|
+
request: Request,
|
|
16
|
+
auth: AuthConfig,
|
|
17
|
+
): Promise<
|
|
18
|
+
| { apiKey: string; convexToken?: string; valid: true }
|
|
19
|
+
| { response: Response; valid: false }
|
|
20
|
+
> {
|
|
21
|
+
const apiKey = extractApiKey(request);
|
|
22
|
+
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
return {
|
|
25
|
+
response: new Response(
|
|
26
|
+
JSON.stringify({
|
|
27
|
+
error:
|
|
28
|
+
"Missing or malformed API key. Use Authorization: Bearer <key>.",
|
|
29
|
+
}),
|
|
30
|
+
{ headers: { "Content-Type": "application/json" }, status: 401 },
|
|
31
|
+
),
|
|
32
|
+
valid: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isValid = await auth.validate(apiKey);
|
|
37
|
+
if (!isValid) {
|
|
38
|
+
return {
|
|
39
|
+
response: new Response(JSON.stringify({ error: "Invalid API key." }), {
|
|
40
|
+
headers: { "Content-Type": "application/json" },
|
|
41
|
+
status: 401,
|
|
42
|
+
}),
|
|
43
|
+
valid: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let convexToken: string | undefined;
|
|
48
|
+
if (auth.convexToken) {
|
|
49
|
+
convexToken = await auth.convexToken(apiKey);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { apiKey, convexToken, valid: true };
|
|
53
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type { ToolPage, ToolSummary } from "./pagination/types.js";
|
|
2
|
+
export { resource } from "./resources/helpers.js";
|
|
3
|
+
export { createMCPServer } from "./server.js";
|
|
4
|
+
export { action, mutation, query } from "./tools/helpers.js";
|
|
5
|
+
export type {
|
|
6
|
+
AuthConfig,
|
|
7
|
+
CallContext,
|
|
8
|
+
ConvexClient,
|
|
9
|
+
ConvexMCPServer,
|
|
10
|
+
ConvexValidator,
|
|
11
|
+
FunctionType,
|
|
12
|
+
LifecycleHooks,
|
|
13
|
+
OnCallResult,
|
|
14
|
+
PaginationConfig,
|
|
15
|
+
ResourceDef,
|
|
16
|
+
ServerConfig,
|
|
17
|
+
ToolDef,
|
|
18
|
+
} from "./types.js";
|
|
19
|
+
export {
|
|
20
|
+
convertValidator,
|
|
21
|
+
convexArgsToZod,
|
|
22
|
+
UnsupportedValidatorError,
|
|
23
|
+
} from "./validators.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PaginationConfig } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/** Create pagination context from config. Validates pageSize >= 1. */
|
|
4
|
+
export function createPaginationContext(config: PaginationConfig | undefined): {
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
pageSize: number;
|
|
7
|
+
secret: string;
|
|
8
|
+
twoPhaseDiscovery: boolean;
|
|
9
|
+
} {
|
|
10
|
+
if (!config) {
|
|
11
|
+
return {
|
|
12
|
+
enabled: false,
|
|
13
|
+
pageSize: 0,
|
|
14
|
+
secret: "",
|
|
15
|
+
twoPhaseDiscovery: false,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (!Number.isInteger(config.pageSize) || config.pageSize < 1) {
|
|
19
|
+
throw new Error("pagination.pageSize must be a positive integer >= 1");
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
enabled: true,
|
|
23
|
+
pageSize: config.pageSize,
|
|
24
|
+
secret: crypto.randomUUID(),
|
|
25
|
+
twoPhaseDiscovery: config.twoPhaseDiscovery ?? false,
|
|
26
|
+
};
|
|
27
|
+
}
|