@tooluminati/forms 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/dist/create-form-tools.d.ts +6 -0
  3. package/dist/create-form-tools.d.ts.map +1 -0
  4. package/dist/create-form-tools.js +96 -0
  5. package/dist/create-form-tools.test.d.ts +2 -0
  6. package/dist/create-form-tools.test.d.ts.map +1 -0
  7. package/dist/create-form-tools.test.js +45 -0
  8. package/dist/errors.d.ts +7 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +9 -0
  11. package/dist/form-tool-definition.d.ts +11 -0
  12. package/dist/form-tool-definition.d.ts.map +1 -0
  13. package/dist/form-tool-definition.js +1 -0
  14. package/dist/formik.d.ts +16 -0
  15. package/dist/formik.d.ts.map +1 -0
  16. package/dist/formik.js +35 -0
  17. package/dist/index.cjs +413 -0
  18. package/dist/index.d.ts +13 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +375 -0
  21. package/dist/infer-schema.d.ts +3 -0
  22. package/dist/infer-schema.d.ts.map +1 -0
  23. package/dist/infer-schema.js +42 -0
  24. package/dist/infer-schema.test.d.ts +2 -0
  25. package/dist/infer-schema.test.d.ts.map +1 -0
  26. package/dist/infer-schema.test.js +27 -0
  27. package/dist/native-form.d.ts +8 -0
  28. package/dist/native-form.d.ts.map +1 -0
  29. package/dist/native-form.js +41 -0
  30. package/dist/react-hook-form.d.ts +21 -0
  31. package/dist/react-hook-form.d.ts.map +1 -0
  32. package/dist/react-hook-form.js +53 -0
  33. package/dist/react-hook-form.test.d.ts +2 -0
  34. package/dist/react-hook-form.test.d.ts.map +1 -0
  35. package/dist/react-hook-form.test.js +28 -0
  36. package/dist/tanstack-form.d.ts +17 -0
  37. package/dist/tanstack-form.d.ts.map +1 -0
  38. package/dist/tanstack-form.js +33 -0
  39. package/dist/types.d.ts +41 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +1 -0
  42. package/dist/useWebMcpFormTool.d.ts +5 -0
  43. package/dist/useWebMcpFormTool.d.ts.map +1 -0
  44. package/dist/useWebMcpFormTool.js +14 -0
  45. package/package.json +54 -0
package/dist/index.js ADDED
@@ -0,0 +1,375 @@
1
+ // src/errors.ts
2
+ var FormValidationError = class extends Error {
3
+ errors;
4
+ constructor(message, errors) {
5
+ super(message);
6
+ this.name = "FormValidationError";
7
+ this.errors = errors;
8
+ }
9
+ };
10
+
11
+ // src/useWebMcpFormTool.ts
12
+ import { useMemo } from "react";
13
+ import { useWebMcpTools } from "@tooluminati/react";
14
+
15
+ // src/infer-schema.ts
16
+ function inferSchemaFromValue(value) {
17
+ if (typeof value === "string") {
18
+ return { type: "string" };
19
+ }
20
+ if (typeof value === "number") {
21
+ return { type: "number" };
22
+ }
23
+ if (typeof value === "boolean") {
24
+ return { type: "boolean" };
25
+ }
26
+ if (value === null || value === void 0) {
27
+ return void 0;
28
+ }
29
+ if (Array.isArray(value)) {
30
+ if (value.length === 0) {
31
+ return void 0;
32
+ }
33
+ const items = inferSchemaFromValue(value[0]);
34
+ return items ? { type: "array", items } : void 0;
35
+ }
36
+ if (typeof value === "object") {
37
+ const properties = {};
38
+ const required = [];
39
+ for (const [key, childValue] of Object.entries(
40
+ value
41
+ )) {
42
+ const childSchema = inferSchemaFromValue(childValue);
43
+ if (!childSchema) {
44
+ return void 0;
45
+ }
46
+ properties[key] = childSchema;
47
+ if (childValue !== void 0) {
48
+ required.push(key);
49
+ }
50
+ }
51
+ return {
52
+ type: "object",
53
+ properties,
54
+ required,
55
+ additionalProperties: false
56
+ };
57
+ }
58
+ return void 0;
59
+ }
60
+
61
+ // src/create-form-tools.ts
62
+ function formatFormErrors(errors, message) {
63
+ const lines = errors.map(
64
+ (error) => `${error.path ? `${error.path}: ` : ""}${error.message || error.kind}`
65
+ );
66
+ return [message, ...lines].filter(Boolean).join("\n");
67
+ }
68
+ function applyRedactResult(result, redactResult) {
69
+ if (!redactResult) {
70
+ return result;
71
+ }
72
+ return redactResult(result);
73
+ }
74
+ function createFormValidationSummaryTool(options) {
75
+ const schema = options.schema ?? inferSchemaFromValue(options.getValues());
76
+ return {
77
+ name: `${options.name}_validation_summary`,
78
+ description: "Returns form schema, validation messages, and submitting/validating state without submitting.",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {},
82
+ additionalProperties: false
83
+ },
84
+ annotations: { readOnlyHint: true },
85
+ execute: () => {
86
+ const summary = {
87
+ name: options.name,
88
+ errors: options.getErrors?.() ?? [],
89
+ schema,
90
+ ...options.getSummary?.()
91
+ };
92
+ if (options.redactValues) {
93
+ return {
94
+ ...summary,
95
+ values: options.redactValues(options.getValues())
96
+ };
97
+ }
98
+ return summary;
99
+ }
100
+ };
101
+ }
102
+ function createFormSubmitTool(options) {
103
+ const schema = options.schema ?? inferSchemaFromValue(options.getValues());
104
+ if (!schema) {
105
+ throw new Error(
106
+ `Could not infer WebMCP schema for form "${options.name}". Provide an explicit schema or use concrete non-null defaults.`
107
+ );
108
+ }
109
+ return {
110
+ name: options.name,
111
+ description: options.description,
112
+ inputSchema: schema,
113
+ annotations: {
114
+ readOnlyHint: false,
115
+ ...options.annotations
116
+ },
117
+ validateArgs: options.validateArgs,
118
+ execute: async (rawArgs) => {
119
+ const values = options.validateArgs ? options.validateArgs(rawArgs) : rawArgs;
120
+ await options.setValues(values);
121
+ const result = await options.submit();
122
+ if (result.success) {
123
+ let output = result.message ?? "Form submitted successfully.";
124
+ if (result.data !== void 0) {
125
+ output = {
126
+ message: output,
127
+ data: options.redactValues ? options.redactValues(result.data) : result.data
128
+ };
129
+ }
130
+ return applyRedactResult(output, options.redactResult);
131
+ }
132
+ const errors = result.errors ?? options.getErrors?.() ?? [];
133
+ return applyRedactResult(
134
+ {
135
+ content: [
136
+ {
137
+ type: "text",
138
+ text: `Form submission failed:
139
+ ${formatFormErrors(
140
+ errors,
141
+ result.message
142
+ )}`
143
+ }
144
+ ],
145
+ isError: true
146
+ },
147
+ options.redactResult
148
+ );
149
+ }
150
+ };
151
+ }
152
+ function createWebMcpFormTools(options) {
153
+ const includeValidationSummary = options.includeValidationSummary ?? true;
154
+ const submitTool = createFormSubmitTool(options);
155
+ if (includeValidationSummary) {
156
+ return [
157
+ submitTool,
158
+ createFormValidationSummaryTool(options)
159
+ ];
160
+ }
161
+ return [submitTool];
162
+ }
163
+
164
+ // src/useWebMcpFormTool.ts
165
+ function useWebMcpFormTool(options, deps = []) {
166
+ const schema = useMemo(
167
+ () => options.schema ?? inferSchemaFromValue(options.getValues()),
168
+ [options]
169
+ );
170
+ if (!schema) {
171
+ throw new Error(
172
+ `Could not infer WebMCP schema for form "${options.name}". Provide an explicit schema or use concrete non-null defaults.`
173
+ );
174
+ }
175
+ const tools = useMemo(
176
+ () => createWebMcpFormTools(options),
177
+ [options]
178
+ );
179
+ useWebMcpTools(tools, deps, { source: "form" });
180
+ }
181
+
182
+ // src/formik.ts
183
+ function flattenErrors(errors, prefix = "") {
184
+ if (!errors || typeof errors !== "object") {
185
+ return [];
186
+ }
187
+ return Object.entries(errors).flatMap(
188
+ ([key, value]) => {
189
+ const path = prefix ? `${prefix}.${key}` : key;
190
+ if (typeof value === "string") {
191
+ return [{ path, message: value }];
192
+ }
193
+ return flattenErrors(value, path);
194
+ }
195
+ );
196
+ }
197
+ function useFormikWebMcpTool({
198
+ formik,
199
+ ...options
200
+ }) {
201
+ useWebMcpFormTool({
202
+ ...options,
203
+ getValues: () => formik.values,
204
+ setValues: (values) => formik.setValues(values),
205
+ getErrors: () => flattenErrors(formik.errors),
206
+ getSummary: () => ({
207
+ submitting: formik.isSubmitting,
208
+ validating: formik.isValidating,
209
+ touched: Boolean(formik.touched)
210
+ }),
211
+ submit: async () => {
212
+ const errors = await formik.validateForm();
213
+ const flattened = flattenErrors(errors);
214
+ if (flattened.length > 0) {
215
+ return { success: false, errors: flattened };
216
+ }
217
+ await formik.submitForm();
218
+ return { success: true };
219
+ }
220
+ });
221
+ }
222
+
223
+ // src/native-form.ts
224
+ function formDataToObject(form) {
225
+ return Object.fromEntries(new FormData(form).entries());
226
+ }
227
+ function useNativeFormWebMcpTool(options) {
228
+ if (!options.schema) {
229
+ throw new Error("Native form WebMCP tools require an explicit schema.");
230
+ }
231
+ useWebMcpFormTool({
232
+ ...options,
233
+ getValues: () => {
234
+ const form = options.formRef.current;
235
+ return form ? formDataToObject(form) : {};
236
+ },
237
+ setValues: (values) => {
238
+ const form = options.formRef.current;
239
+ if (!form) {
240
+ return;
241
+ }
242
+ for (const [name, value] of Object.entries(values)) {
243
+ const field = form.elements.namedItem(name);
244
+ if (field && "value" in field) {
245
+ field.value = String(value ?? "");
246
+ }
247
+ }
248
+ },
249
+ getErrors: () => [],
250
+ submit: () => {
251
+ const form = options.formRef.current;
252
+ if (!form) {
253
+ return { success: false, message: "Form is not mounted." };
254
+ }
255
+ if (!form.checkValidity()) {
256
+ form.reportValidity();
257
+ return { success: false, message: "Native form validation failed." };
258
+ }
259
+ form.requestSubmit();
260
+ return { success: true, message: "Native form submitted." };
261
+ }
262
+ });
263
+ }
264
+
265
+ // src/react-hook-form.ts
266
+ function flattenReactHookFormErrors(errors, prefix = "") {
267
+ if (!errors || typeof errors !== "object") {
268
+ return [];
269
+ }
270
+ return Object.entries(errors).flatMap(
271
+ ([key, value]) => {
272
+ const path = prefix ? `${prefix}.${key}` : key;
273
+ if (value?.message || value?.type) {
274
+ return [
275
+ {
276
+ path,
277
+ message: String(value.message ?? value.type),
278
+ kind: value.type ? String(value.type) : void 0
279
+ }
280
+ ];
281
+ }
282
+ return flattenReactHookFormErrors(value, path);
283
+ }
284
+ );
285
+ }
286
+ function useReactHookFormWebMcpTool({
287
+ form,
288
+ onValidSubmit,
289
+ ...options
290
+ }) {
291
+ useWebMcpFormTool({
292
+ ...options,
293
+ getValues: () => form.getValues(),
294
+ setValues: async (values) => {
295
+ form.reset(values);
296
+ await form.trigger();
297
+ },
298
+ getErrors: () => flattenReactHookFormErrors(form.formState.errors),
299
+ getSummary: () => ({
300
+ submitting: form.formState.isSubmitting,
301
+ validating: form.formState.isValidating,
302
+ dirty: form.formState.isDirty,
303
+ touched: Boolean(form.formState.touchedFields)
304
+ }),
305
+ submit: async () => {
306
+ let valid = false;
307
+ let data;
308
+ await form.handleSubmit(
309
+ async (values) => {
310
+ valid = true;
311
+ data = await onValidSubmit(values);
312
+ },
313
+ async () => {
314
+ valid = false;
315
+ }
316
+ )();
317
+ if (valid) {
318
+ return { success: true, data };
319
+ }
320
+ return {
321
+ success: false,
322
+ errors: flattenReactHookFormErrors(form.formState.errors)
323
+ };
324
+ }
325
+ });
326
+ }
327
+
328
+ // src/tanstack-form.ts
329
+ function flattenTanStackErrors(errors = []) {
330
+ return errors.map((error, index) => ({
331
+ path: typeof error === "object" && error && "path" in error ? String(error.path) : String(index),
332
+ message: typeof error === "object" && error && "message" in error ? String(error.message) : String(error)
333
+ }));
334
+ }
335
+ function useTanStackFormWebMcpTool({
336
+ form,
337
+ ...options
338
+ }) {
339
+ useWebMcpFormTool({
340
+ ...options,
341
+ getValues: () => form.state.values,
342
+ setValues: (values) => {
343
+ for (const [field, value] of Object.entries(
344
+ values
345
+ )) {
346
+ form.setFieldValue?.(field, value);
347
+ }
348
+ },
349
+ getErrors: () => flattenTanStackErrors(form.state.errors),
350
+ getSummary: () => ({
351
+ submitting: form.state.isSubmitting,
352
+ validating: form.state.isValidating,
353
+ dirty: form.state.isDirty
354
+ }),
355
+ submit: async () => {
356
+ await form.handleSubmit();
357
+ const errors = flattenTanStackErrors(form.state.errors);
358
+ return errors.length > 0 ? { success: false, errors } : { success: true };
359
+ }
360
+ });
361
+ }
362
+ export {
363
+ FormValidationError,
364
+ createFormSubmitTool,
365
+ createFormValidationSummaryTool,
366
+ createWebMcpFormTools,
367
+ flattenReactHookFormErrors,
368
+ formDataToObject,
369
+ inferSchemaFromValue,
370
+ useFormikWebMcpTool,
371
+ useNativeFormWebMcpTool,
372
+ useReactHookFormWebMcpTool,
373
+ useTanStackFormWebMcpTool,
374
+ useWebMcpFormTool
375
+ };
@@ -0,0 +1,3 @@
1
+ import type { JsonSchema } from '@tooluminati/core';
2
+ export declare function inferSchemaFromValue(value: unknown): JsonSchema | undefined;
3
+ //# sourceMappingURL=infer-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infer-schema.d.ts","sourceRoot":"","sources":["../src/infer-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAqD3E"}
@@ -0,0 +1,42 @@
1
+ export function inferSchemaFromValue(value) {
2
+ if (typeof value === 'string') {
3
+ return { type: 'string' };
4
+ }
5
+ if (typeof value === 'number') {
6
+ return { type: 'number' };
7
+ }
8
+ if (typeof value === 'boolean') {
9
+ return { type: 'boolean' };
10
+ }
11
+ if (value === null || value === undefined) {
12
+ return undefined;
13
+ }
14
+ if (Array.isArray(value)) {
15
+ if (value.length === 0) {
16
+ return undefined;
17
+ }
18
+ const items = inferSchemaFromValue(value[0]);
19
+ return items ? { type: 'array', items } : undefined;
20
+ }
21
+ if (typeof value === 'object') {
22
+ const properties = {};
23
+ const required = [];
24
+ for (const [key, childValue] of Object.entries(value)) {
25
+ const childSchema = inferSchemaFromValue(childValue);
26
+ if (!childSchema) {
27
+ return undefined;
28
+ }
29
+ properties[key] = childSchema;
30
+ if (childValue !== undefined) {
31
+ required.push(key);
32
+ }
33
+ }
34
+ return {
35
+ type: 'object',
36
+ properties,
37
+ required,
38
+ additionalProperties: false,
39
+ };
40
+ }
41
+ return undefined;
42
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=infer-schema.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infer-schema.test.d.ts","sourceRoot":"","sources":["../src/infer-schema.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { inferSchemaFromValue } from './infer-schema';
3
+ describe('inferSchemaFromValue', () => {
4
+ it('infers primitive types', () => {
5
+ expect(inferSchemaFromValue('hello')).toEqual({ type: 'string' });
6
+ expect(inferSchemaFromValue(42)).toEqual({ type: 'number' });
7
+ expect(inferSchemaFromValue(3.14)).toEqual({ type: 'number' });
8
+ expect(inferSchemaFromValue(true)).toEqual({ type: 'boolean' });
9
+ });
10
+ it('returns undefined for null, undefined, and empty arrays', () => {
11
+ expect(inferSchemaFromValue(null)).toBeUndefined();
12
+ expect(inferSchemaFromValue(undefined)).toBeUndefined();
13
+ expect(inferSchemaFromValue([])).toBeUndefined();
14
+ });
15
+ it('infers nested object schemas', () => {
16
+ expect(inferSchemaFromValue({ name: 'Ada', age: 30, active: true })).toEqual({
17
+ type: 'object',
18
+ properties: {
19
+ name: { type: 'string' },
20
+ age: { type: 'number' },
21
+ active: { type: 'boolean' },
22
+ },
23
+ required: ['name', 'age', 'active'],
24
+ additionalProperties: false,
25
+ });
26
+ });
27
+ });
@@ -0,0 +1,8 @@
1
+ import type { RefObject } from 'react';
2
+ import type { WebMcpFormToolOptions } from './types';
3
+ export interface NativeFormWebMcpOptions extends Pick<WebMcpFormToolOptions<Record<string, unknown>>, 'name' | 'description' | 'schema' | 'validateArgs' | 'annotations'> {
4
+ formRef: RefObject<HTMLFormElement | null>;
5
+ }
6
+ export declare function formDataToObject(form: HTMLFormElement): Record<string, unknown>;
7
+ export declare function useNativeFormWebMcpTool(options: NativeFormWebMcpOptions): void;
8
+ //# sourceMappingURL=native-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-form.d.ts","sourceRoot":"","sources":["../src/native-form.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD,MAAM,WAAW,uBACf,SAAQ,IAAI,CACV,qBAAqB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC9C,MAAM,GAAG,aAAa,GAAG,QAAQ,GAAG,cAAc,GAAG,aAAa,CACnE;IACD,OAAO,EAAE,SAAS,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CAC5C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE/E;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,uBAAuB,GAC/B,IAAI,CAwCN"}
@@ -0,0 +1,41 @@
1
+ import { useWebMcpFormTool } from './useWebMcpFormTool';
2
+ export function formDataToObject(form) {
3
+ return Object.fromEntries(new FormData(form).entries());
4
+ }
5
+ export function useNativeFormWebMcpTool(options) {
6
+ if (!options.schema) {
7
+ throw new Error('Native form WebMCP tools require an explicit schema.');
8
+ }
9
+ useWebMcpFormTool({
10
+ ...options,
11
+ getValues: () => {
12
+ const form = options.formRef.current;
13
+ return form ? formDataToObject(form) : {};
14
+ },
15
+ setValues: (values) => {
16
+ const form = options.formRef.current;
17
+ if (!form) {
18
+ return;
19
+ }
20
+ for (const [name, value] of Object.entries(values)) {
21
+ const field = form.elements.namedItem(name);
22
+ if (field && 'value' in field) {
23
+ field.value = String(value ?? '');
24
+ }
25
+ }
26
+ },
27
+ getErrors: () => [],
28
+ submit: () => {
29
+ const form = options.formRef.current;
30
+ if (!form) {
31
+ return { success: false, message: 'Form is not mounted.' };
32
+ }
33
+ if (!form.checkValidity()) {
34
+ form.reportValidity();
35
+ return { success: false, message: 'Native form validation failed.' };
36
+ }
37
+ form.requestSubmit();
38
+ return { success: true, message: 'Native form submitted.' };
39
+ },
40
+ });
41
+ }
@@ -0,0 +1,21 @@
1
+ import type { FormError, WebMcpFormToolOptions } from './types';
2
+ export interface ReactHookFormLike<TValues> {
3
+ getValues(): TValues;
4
+ reset(values: TValues): void;
5
+ trigger(): Promise<boolean>;
6
+ handleSubmit(onValid: (values: TValues) => void | Promise<void>, onInvalid?: () => void | Promise<void>): () => Promise<void> | void;
7
+ formState: {
8
+ errors?: unknown;
9
+ isSubmitting?: boolean;
10
+ isValidating?: boolean;
11
+ isDirty?: boolean;
12
+ touchedFields?: unknown;
13
+ };
14
+ }
15
+ export interface ReactHookFormWebMcpOptions<TValues> extends Pick<WebMcpFormToolOptions<TValues>, 'name' | 'description' | 'schema' | 'validateArgs' | 'annotations'> {
16
+ form: ReactHookFormLike<TValues>;
17
+ onValidSubmit: (values: TValues) => unknown | Promise<unknown>;
18
+ }
19
+ export declare function flattenReactHookFormErrors(errors: unknown, prefix?: string): FormError[];
20
+ export declare function useReactHookFormWebMcpTool<TValues>({ form, onValidSubmit, ...options }: ReactHookFormWebMcpOptions<TValues>): void;
21
+ //# sourceMappingURL=react-hook-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-hook-form.d.ts","sourceRoot":"","sources":["../src/react-hook-form.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,WAAW,iBAAiB,CAAC,OAAO;IACxC,SAAS,IAAI,OAAO,CAAC;IACrB,KAAK,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,YAAY,CACV,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAClD,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACrC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE;QACT,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,CAAC;CACH;AAED,MAAM,WAAW,0BAA0B,CAAC,OAAO,CACjD,SAAQ,IAAI,CACV,qBAAqB,CAAC,OAAO,CAAC,EAC9B,MAAM,GAAG,aAAa,GAAG,QAAQ,GAAG,cAAc,GAAG,aAAa,CACnE;IACD,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChE;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,OAAO,EACf,MAAM,SAAK,GACV,SAAS,EAAE,CAqBb;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,EAClD,IAAI,EACJ,aAAa,EACb,GAAG,OAAO,EACX,EAAE,0BAA0B,CAAC,OAAO,CAAC,GAAG,IAAI,CAuC5C"}
@@ -0,0 +1,53 @@
1
+ import { useWebMcpFormTool } from './useWebMcpFormTool';
2
+ export function flattenReactHookFormErrors(errors, prefix = '') {
3
+ if (!errors || typeof errors !== 'object') {
4
+ return [];
5
+ }
6
+ return Object.entries(errors).flatMap(([key, value]) => {
7
+ const path = prefix ? `${prefix}.${key}` : key;
8
+ if (value?.message || value?.type) {
9
+ return [
10
+ {
11
+ path,
12
+ message: String(value.message ?? value.type),
13
+ kind: value.type ? String(value.type) : undefined,
14
+ },
15
+ ];
16
+ }
17
+ return flattenReactHookFormErrors(value, path);
18
+ });
19
+ }
20
+ export function useReactHookFormWebMcpTool({ form, onValidSubmit, ...options }) {
21
+ useWebMcpFormTool({
22
+ ...options,
23
+ getValues: () => form.getValues(),
24
+ setValues: async (values) => {
25
+ form.reset(values);
26
+ await form.trigger();
27
+ },
28
+ getErrors: () => flattenReactHookFormErrors(form.formState.errors),
29
+ getSummary: () => ({
30
+ submitting: form.formState.isSubmitting,
31
+ validating: form.formState.isValidating,
32
+ dirty: form.formState.isDirty,
33
+ touched: Boolean(form.formState.touchedFields),
34
+ }),
35
+ submit: async () => {
36
+ let valid = false;
37
+ let data;
38
+ await form.handleSubmit(async (values) => {
39
+ valid = true;
40
+ data = await onValidSubmit(values);
41
+ }, async () => {
42
+ valid = false;
43
+ })();
44
+ if (valid) {
45
+ return { success: true, data };
46
+ }
47
+ return {
48
+ success: false,
49
+ errors: flattenReactHookFormErrors(form.formState.errors),
50
+ };
51
+ },
52
+ });
53
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=react-hook-form.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-hook-form.test.d.ts","sourceRoot":"","sources":["../src/react-hook-form.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { flattenReactHookFormErrors } from './react-hook-form';
3
+ describe('flattenReactHookFormErrors', () => {
4
+ it('flattens nested react-hook-form errors', () => {
5
+ const errors = flattenReactHookFormErrors({
6
+ email: { type: 'required', message: 'Email is required' },
7
+ profile: {
8
+ name: { type: 'min', message: 'Name is too short' },
9
+ },
10
+ });
11
+ expect(errors).toEqual([
12
+ {
13
+ path: 'email',
14
+ message: 'Email is required',
15
+ kind: 'required',
16
+ },
17
+ {
18
+ path: 'profile.name',
19
+ message: 'Name is too short',
20
+ kind: 'min',
21
+ },
22
+ ]);
23
+ });
24
+ it('returns an empty array for non-object errors', () => {
25
+ expect(flattenReactHookFormErrors(null)).toEqual([]);
26
+ expect(flattenReactHookFormErrors(undefined)).toEqual([]);
27
+ });
28
+ });
@@ -0,0 +1,17 @@
1
+ import type { WebMcpFormToolOptions } from './types';
2
+ export interface TanStackFormLike<TValues> {
3
+ state: {
4
+ values: TValues;
5
+ errors?: unknown[];
6
+ isSubmitting?: boolean;
7
+ isValidating?: boolean;
8
+ isDirty?: boolean;
9
+ };
10
+ setFieldValue?: (field: string, value: unknown) => void;
11
+ handleSubmit: () => void | Promise<void>;
12
+ }
13
+ export interface TanStackFormWebMcpOptions<TValues> extends Pick<WebMcpFormToolOptions<TValues>, 'name' | 'description' | 'schema' | 'validateArgs' | 'annotations'> {
14
+ form: TanStackFormLike<TValues>;
15
+ }
16
+ export declare function useTanStackFormWebMcpTool<TValues>({ form, ...options }: TanStackFormWebMcpOptions<TValues>): void;
17
+ //# sourceMappingURL=tanstack-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tanstack-form.d.ts","sourceRoot":"","sources":["../src/tanstack-form.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,WAAW,gBAAgB,CAAC,OAAO;IACvC,KAAK,EAAE;QACL,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;QACnB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,YAAY,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,yBAAyB,CAAC,OAAO,CAChD,SAAQ,IAAI,CACV,qBAAqB,CAAC,OAAO,CAAC,EAC9B,MAAM,GAAG,aAAa,GAAG,QAAQ,GAAG,cAAc,GAAG,aAAa,CACnE;IACD,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;CACjC;AAeD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,EACjD,IAAI,EACJ,GAAG,OAAO,EACX,EAAE,yBAAyB,CAAC,OAAO,CAAC,GAAG,IAAI,CAuB3C"}