simple-webmcp 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.
package/dist/react.js ADDED
@@ -0,0 +1,106 @@
1
+ import { useState, useMemo, useRef, useEffect } from 'react';
2
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
+
4
+ // src/internal/utils.ts
5
+ function isWebMCPSupported() {
6
+ if (typeof document === "undefined") return false;
7
+ const docAny = document;
8
+ return !!docAny.modelContext && typeof docAny.modelContext.registerTool === "function";
9
+ }
10
+
11
+ // src/react/useWebMCP.ts
12
+ function isTool(v) {
13
+ return !!v?.__webmcpBrand;
14
+ }
15
+ function useWebMCP(tool, opts) {
16
+ const enabled = opts?.enabled ?? true;
17
+ const [registered, setRegistered] = useState(false);
18
+ const [error, setError] = useState(null);
19
+ const supported = useMemo(() => isWebMCPSupported(), []);
20
+ const isPolyfilled = useMemo(() => {
21
+ if (typeof document === "undefined") return false;
22
+ const docAny = document;
23
+ return !!docAny.modelContext?._isPolyfill;
24
+ }, [supported]);
25
+ const toolRef = useRef(tool);
26
+ toolRef.current = tool;
27
+ useEffect(() => {
28
+ if (!enabled) {
29
+ setRegistered(false);
30
+ setError(null);
31
+ return;
32
+ }
33
+ const t = toolRef.current;
34
+ if (!isTool(t)) {
35
+ if (typeof console !== "undefined") {
36
+ console.warn("[simple-webmcp] useWebMCP expected webmcp(fn) tool, got plain function. Wrap with webmcp() first.");
37
+ }
38
+ setError(new Error("useWebMCP: wrap fn with webmcp() first"));
39
+ setRegistered(false);
40
+ return;
41
+ }
42
+ const webTool = t;
43
+ if (webTool.status === "registered") {
44
+ setRegistered(true);
45
+ setError(null);
46
+ return;
47
+ }
48
+ let cancelled = false;
49
+ let unregister = null;
50
+ const controller = new AbortController();
51
+ webTool.register({ signal: controller.signal }).then((u) => {
52
+ if (cancelled) {
53
+ try {
54
+ u();
55
+ } catch {
56
+ }
57
+ return;
58
+ }
59
+ unregister = u;
60
+ setRegistered(true);
61
+ setError(null);
62
+ }).catch((e) => {
63
+ if (cancelled) return;
64
+ setRegistered(false);
65
+ const err = e instanceof Error ? e : new Error(String(e));
66
+ setError(err);
67
+ });
68
+ return () => {
69
+ cancelled = true;
70
+ try {
71
+ controller.abort();
72
+ } catch {
73
+ }
74
+ if (unregister) {
75
+ try {
76
+ unregister();
77
+ } catch {
78
+ }
79
+ } else {
80
+ try {
81
+ webTool.unregister?.();
82
+ } catch {
83
+ }
84
+ }
85
+ setRegistered(false);
86
+ };
87
+ }, [enabled, supported, tool]);
88
+ const status = !supported ? "unsupported" : error ? "error" : registered ? "registered" : enabled ? "registering" : "unregistered";
89
+ return { supported: !!supported, registered, error, isPolyfilled, status };
90
+ }
91
+ function ToolRegistrar({ tool, enabled }) {
92
+ useWebMCP(tool, { enabled });
93
+ return null;
94
+ }
95
+ function Scope({ tools, enabled = true, children }) {
96
+ const normalized = useMemo(() => tools ?? [], [tools]);
97
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
98
+ normalized.map((t, i) => /* @__PURE__ */ jsx(ToolRegistrar, { tool: t, enabled }, t?.tool?.name ?? t?.definition?.name ?? `tool-${i}`)),
99
+ children
100
+ ] });
101
+ }
102
+ var WebMCPScope = Scope;
103
+
104
+ export { Scope, WebMCPScope, useWebMCP };
105
+ //# sourceMappingURL=react.js.map
106
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/utils.ts","../src/react/useWebMCP.ts","../src/react/Scope.tsx"],"names":["useMemo"],"mappings":"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","file":"react.js","sourcesContent":["import type { JsonSchema, ModelContextLike } from '../types.js';\n\n/**\n * Convert camelCase / PascalCase to snake_case for tool naming.\n * searchCustomers → search_customers\n * SearchCustomers → search_customers\n */\nexport function toSnakeCase(name: string): string {\n if (!name) return 'anonymous_tool';\n return name\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')\n .toLowerCase()\n .replace(/[^a-z0-9_]+/g, '_')\n .replace(/^_+|_+$/g, '') || 'anonymous_tool';\n}\n\n/**\n * Totally minimal doc extraction — best effort.\n * Full JSDoc inference is build-time. Runtime we just note presence.\n */\nexport function getFunctionName(fn: Function): string {\n return (fn as any).displayName || fn.name || '';\n}\n\n/**\n * Check if WebMCP is supported (native or polyfilled)\n */\nexport function isWebMCPSupported(): boolean {\n if (typeof document === 'undefined') return false;\n const docAny = document as any;\n return !!docAny.modelContext && typeof docAny.modelContext.registerTool === 'function';\n}\n\nexport function getModelContext(): ModelContextLike | null {\n if (typeof document === 'undefined') return null;\n const docAny = document as any;\n if (docAny.modelContext && typeof docAny.modelContext.registerTool === 'function') {\n return docAny.modelContext as ModelContextLike;\n }\n return null;\n}\n\n/**\n * Heuristic check if value looks like JSON Schema (vs StandardSchema)\n */\nexport function looksLikeJsonSchema(value: unknown): value is JsonSchema {\n if (!value || typeof value !== 'object') return false;\n const obj = value as Record<string, unknown>;\n // JSON Schema typically has `type` at top, no `~standard`\n if ('~standard' in obj) return false;\n // Has type or properties or $ref etc\n return (\n 'type' in obj ||\n 'properties' in obj ||\n '$ref' in obj ||\n 'anyOf' in obj ||\n 'oneOf' in obj ||\n 'allOf' in obj ||\n 'enum' in obj\n );\n}\n\n/**\n * Check if value is StandardSchema\n */\nexport function isStandardSchema(value: unknown): boolean {\n return (\n !!value &&\n typeof value === 'object' &&\n '~standard' in (value as any) &&\n (value as any)['~standard']?.validate != null &&\n typeof (value as any)['~standard'].validate === 'function'\n );\n}\n\n/**\n * Cheap JSDoc/description fallback — runtime can't parse JSDoc,\n * but we check if fn has __webmcpDescription attached by build plugin or manual.\n */\nexport function getDescription(fn: Function, override?: string): string {\n if (override != null) return override;\n const anyFn = fn as any;\n if (typeof anyFn.__webmcpDescription === 'string') return anyFn.__webmcpDescription;\n // No JSDoc at runtime — return empty and warn elsewhere\n return '';\n}\n\nlet warnedNoDesc = false;\nexport function warnNoDescription(fn: Function) {\n if (warnedNoDesc) return;\n // Only warn once per page load\n warnedNoDesc = true;\n if (typeof console !== 'undefined' && typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n // eslint-disable-next-line no-console\n console.warn(\n `[simple-webmcp] tool \"${getFunctionName(fn) || 'anonymous'}\" has no description. Add \\`description:\"...\"\\` or JSDoc. See https://github.com/emingure/simple-webmcp`,\n );\n } else if (typeof console !== 'undefined') {\n // In browser dev, also warn once\n // eslint-disable-next-line no-console\n console.warn(`[simple-webmcp] tool \"${getFunctionName(fn) || 'anonymous'}\" has no description.`);\n }\n}\n","'use client';\n\nimport { useEffect, useState, useRef, useMemo } from 'react';\nimport type { WebMCPTool } from '../types.js';\nimport { isWebMCPSupported } from '../internal/utils.js';\nimport { NotSupportedError } from '../errors.js';\n\n// Check helper to see if value is a WebMCPTool\nfunction isTool<F extends (...a: any) => any>(v: WebMCPTool<F> | F): v is WebMCPTool<F> {\n return !!(v as any)?.__webmcpBrand;\n}\n\nexport type UseWebMCPResult = {\n supported: boolean;\n registered: boolean;\n error: Error | null;\n isPolyfilled: boolean;\n status: 'unregistered' | 'registering' | 'registered' | 'error' | 'unsupported';\n};\n\n/**\n * Register a tool for the lifetime of the component.\n * Matches WebMCP spec AbortSignal lifecycle and StrictMode double-mount safety via dedup registry.\n *\n * @param tool - WebMCPTool from webmcp(fn) or raw fn (will warn and no-op)\n * @param opts.enabled - if false, inert\n */\nexport function useWebMCP<F extends (...args: any) => any>(\n tool: WebMCPTool<F> | F,\n opts?: { enabled?: boolean },\n): UseWebMCPResult {\n const enabled = opts?.enabled ?? true;\n const [registered, setRegistered] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const supported = useMemo(() => isWebMCPSupported(), []);\n // polyfill detection — if isWebMCPSupported true and shim marker present\n const isPolyfilled = useMemo(() => {\n if (typeof document === 'undefined') return false;\n const docAny = document as any;\n return !!docAny.modelContext?._isPolyfill;\n }, [supported]);\n\n const toolRef = useRef(tool);\n toolRef.current = tool;\n\n useEffect(() => {\n if (!enabled) {\n setRegistered(false);\n setError(null);\n return;\n }\n\n const t: any = toolRef.current as any;\n\n if (!isTool(t as any)) {\n // Plain fn passed — warn, optionally auto-wrap? For 0.1 we no-op with warning\n if (typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.warn('[simple-webmcp] useWebMCP expected webmcp(fn) tool, got plain function. Wrap with webmcp() first.');\n }\n setError(new Error('useWebMCP: wrap fn with webmcp() first'));\n setRegistered(false);\n return;\n }\n\n const webTool = t as WebMCPTool<F>;\n\n // If tool was created with global:true, it's already global — just track status\n // We still sync registered state\n if ((webTool as any).status === 'registered') {\n setRegistered(true);\n setError(null);\n return;\n }\n\n let cancelled = false;\n let unregister: (() => void) | null = null;\n\n // Async register — hook's job is to handle AbortSignal mapping\n const controller = new AbortController();\n\n webTool\n .register({ signal: controller.signal })\n .then((u) => {\n if (cancelled) {\n try {\n u();\n } catch {}\n return;\n }\n unregister = u;\n setRegistered(true);\n setError(null);\n })\n .catch((e: unknown) => {\n if (cancelled) return;\n setRegistered(false);\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err);\n });\n\n return () => {\n cancelled = true;\n try {\n controller.abort();\n } catch {}\n if (unregister) {\n try {\n unregister();\n } catch {}\n } else {\n // If registration still pending, ensure tool's own unregister clears\n try {\n (webTool as any).unregister?.();\n } catch {}\n }\n setRegistered(false);\n };\n // Re-run if tool identity changes or enabled\n }, [enabled, supported, tool]);\n\n const status = !supported ? 'unsupported' : error ? 'error' : registered ? 'registered' : enabled ? 'registering' : 'unregistered';\n\n return { supported: !!supported, registered, error, isPolyfilled, status: status as any };\n}\n","'use client';\n\nimport React, { useMemo } from 'react';\nimport type { WebMCPTool } from '../types.js';\nimport { useWebMCP } from './useWebMCP.js';\n\nexport type ScopeProps = {\n tools: Array<WebMCPTool<any>>;\n enabled?: boolean;\n children?: React.ReactNode;\n};\n\n/**\n * React subtree scope — exposes tools while this component is mounted.\n * Maps cleanly onto WebMCP AbortSignal lifecycle.\n * Naturally gives route-level scope when placed in Next.js layout.tsx.\n */\nfunction ToolRegistrar({ tool, enabled }: { tool: WebMCPTool<any>; enabled?: boolean }) {\n useWebMCP(tool as any, { enabled });\n return null;\n}\n\nexport function Scope({ tools, enabled = true, children }: ScopeProps) {\n const normalized = useMemo(() => tools ?? [], [tools]);\n return (\n <>\n {normalized.map((t, i) => (\n <ToolRegistrar key={(t as any)?.tool?.name ?? (t as any)?.definition?.name ?? `tool-${i}`} tool={t} enabled={enabled} />\n ))}\n {children}\n </>\n );\n}\n\n// Also export as WebMCP.Scope style\nexport const WebMCPScope = Scope;\n"]}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Minimal JSON Schema type used for WebMCP input/output schemas.
3
+ * We keep it loose (Partial) to avoid hard dependency on a specific draft.
4
+ */
5
+ type JsonSchema = {
6
+ type?: string;
7
+ title?: string;
8
+ description?: string;
9
+ properties?: Record<string, JsonSchema>;
10
+ required?: string[];
11
+ items?: JsonSchema;
12
+ enum?: unknown[];
13
+ default?: unknown;
14
+ minimum?: number;
15
+ maximum?: number;
16
+ minLength?: number;
17
+ maxLength?: number;
18
+ pattern?: string;
19
+ format?: string;
20
+ additionalProperties?: boolean | JsonSchema;
21
+ anyOf?: JsonSchema[];
22
+ oneOf?: JsonSchema[];
23
+ allOf?: JsonSchema[];
24
+ $ref?: string;
25
+ [key: string]: unknown;
26
+ };
27
+ /**
28
+ * Standard Schema spec v1 (subset) — https://github.com/standard-schema/standard-schema
29
+ * We support any object that exposes `~standard`.
30
+ */
31
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
32
+ readonly '~standard': {
33
+ readonly version: 1;
34
+ readonly vendor: string;
35
+ readonly validate: (value: unknown) => {
36
+ value: Output;
37
+ } | {
38
+ issues: Array<{
39
+ message: string;
40
+ path?: (string | number)[];
41
+ }>;
42
+ };
43
+ readonly types?: {
44
+ readonly input: Input;
45
+ readonly output: Output;
46
+ };
47
+ };
48
+ }
49
+ type FieldDef = Partial<JsonSchema>;
50
+ type FieldDefOrSchema = FieldDef | StandardSchemaV1;
51
+ type WebMCPAnnotations = Record<string, unknown> & {
52
+ readOnlyHint?: boolean;
53
+ destructiveHint?: boolean;
54
+ openWorldHint?: boolean;
55
+ title?: string;
56
+ };
57
+ type ToolContract = {
58
+ name: string;
59
+ description: string;
60
+ inputSchema: JsonSchema;
61
+ outputSchema?: JsonSchema;
62
+ annotations?: WebMCPAnnotations;
63
+ };
64
+ type WebMCPOptions<F extends (...args: any) => any> = {
65
+ /** Override tool name. Default: fn.name → snake_case */
66
+ name?: string;
67
+ /** Override description. Default: JSDoc or '' + dev warn */
68
+ description?: string;
69
+ /**
70
+ * Whole input schema — StandardSchema (Zod, Valibot, ArkType, etc) or JSON Schema.
71
+ * When provided, this establishes the contract. `fields` then patches it.
72
+ */
73
+ schema?: JsonSchema | StandardSchemaV1;
74
+ /** Same as `schema` internal alias is not needed externally — deprecated */
75
+ /** Output schema (optional, not validated v0.1) */
76
+ outputSchema?: JsonSchema | StandardSchemaV1;
77
+ /**
78
+ * Per-field patch — most useful for adding descriptions/min/max without rewriting whole schema.
79
+ * Values can be Partial<JsonSchema> or StandardSchema (per-field Zod).
80
+ */
81
+ fields?: Record<string, FieldDefOrSchema>;
82
+ /** Annotations forwarded to WebMCP (readOnlyHint, etc) — extensible */
83
+ annotations?: WebMCPAnnotations;
84
+ /** Scope — global registers immediately, scoped requires useWebMCP, manual requires explicit register() */
85
+ scope?: 'global' | 'scoped' | 'manual';
86
+ /** Alias for scope='global' */
87
+ global?: boolean;
88
+ /** Enable/disable registration (default true) */
89
+ enabled?: boolean;
90
+ /** If true, ambiguous inference throws instead of warn */
91
+ strict?: boolean;
92
+ };
93
+ type WebMCPTool<F extends (...args: any) => any> = F & {
94
+ readonly __webmcpBrand: true;
95
+ readonly tool: ToolContract;
96
+ readonly definition: ToolContract;
97
+ /** Async register — returns unregister fn */
98
+ register: (opts?: {
99
+ signal?: AbortSignal;
100
+ }) => Promise<() => void>;
101
+ /** Unregister (sync convenience) */
102
+ unregister: () => void;
103
+ /** Current status */
104
+ readonly status: RegistrationStatus;
105
+ /** Promise for pending registration */
106
+ readonly registration: Promise<void> | null;
107
+ isRegistered: () => boolean;
108
+ readonly __fn: F;
109
+ readonly __standardSchema?: StandardSchemaV1;
110
+ readonly __outputStandardSchema?: StandardSchemaV1;
111
+ };
112
+ type RegistrationStatus = 'unregistered' | 'registering' | 'registered' | 'unregistering' | 'error';
113
+ type ModelContextLike = {
114
+ registerTool: (def: {
115
+ name: string;
116
+ description?: string;
117
+ inputSchema?: JsonSchema;
118
+ outputSchema?: JsonSchema;
119
+ annotations?: WebMCPAnnotations;
120
+ execute: (args: unknown, ctx?: unknown) => unknown;
121
+ }, opts?: {
122
+ signal?: AbortSignal;
123
+ }) => Promise<void>;
124
+ };
125
+
126
+ export type { FieldDefOrSchema as F, JsonSchema as J, ModelContextLike as M, RegistrationStatus as R, StandardSchemaV1 as S, ToolContract as T, WebMCPOptions as W, WebMCPTool as a, FieldDef as b, WebMCPAnnotations as c };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Minimal JSON Schema type used for WebMCP input/output schemas.
3
+ * We keep it loose (Partial) to avoid hard dependency on a specific draft.
4
+ */
5
+ type JsonSchema = {
6
+ type?: string;
7
+ title?: string;
8
+ description?: string;
9
+ properties?: Record<string, JsonSchema>;
10
+ required?: string[];
11
+ items?: JsonSchema;
12
+ enum?: unknown[];
13
+ default?: unknown;
14
+ minimum?: number;
15
+ maximum?: number;
16
+ minLength?: number;
17
+ maxLength?: number;
18
+ pattern?: string;
19
+ format?: string;
20
+ additionalProperties?: boolean | JsonSchema;
21
+ anyOf?: JsonSchema[];
22
+ oneOf?: JsonSchema[];
23
+ allOf?: JsonSchema[];
24
+ $ref?: string;
25
+ [key: string]: unknown;
26
+ };
27
+ /**
28
+ * Standard Schema spec v1 (subset) — https://github.com/standard-schema/standard-schema
29
+ * We support any object that exposes `~standard`.
30
+ */
31
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
32
+ readonly '~standard': {
33
+ readonly version: 1;
34
+ readonly vendor: string;
35
+ readonly validate: (value: unknown) => {
36
+ value: Output;
37
+ } | {
38
+ issues: Array<{
39
+ message: string;
40
+ path?: (string | number)[];
41
+ }>;
42
+ };
43
+ readonly types?: {
44
+ readonly input: Input;
45
+ readonly output: Output;
46
+ };
47
+ };
48
+ }
49
+ type FieldDef = Partial<JsonSchema>;
50
+ type FieldDefOrSchema = FieldDef | StandardSchemaV1;
51
+ type WebMCPAnnotations = Record<string, unknown> & {
52
+ readOnlyHint?: boolean;
53
+ destructiveHint?: boolean;
54
+ openWorldHint?: boolean;
55
+ title?: string;
56
+ };
57
+ type ToolContract = {
58
+ name: string;
59
+ description: string;
60
+ inputSchema: JsonSchema;
61
+ outputSchema?: JsonSchema;
62
+ annotations?: WebMCPAnnotations;
63
+ };
64
+ type WebMCPOptions<F extends (...args: any) => any> = {
65
+ /** Override tool name. Default: fn.name → snake_case */
66
+ name?: string;
67
+ /** Override description. Default: JSDoc or '' + dev warn */
68
+ description?: string;
69
+ /**
70
+ * Whole input schema — StandardSchema (Zod, Valibot, ArkType, etc) or JSON Schema.
71
+ * When provided, this establishes the contract. `fields` then patches it.
72
+ */
73
+ schema?: JsonSchema | StandardSchemaV1;
74
+ /** Same as `schema` internal alias is not needed externally — deprecated */
75
+ /** Output schema (optional, not validated v0.1) */
76
+ outputSchema?: JsonSchema | StandardSchemaV1;
77
+ /**
78
+ * Per-field patch — most useful for adding descriptions/min/max without rewriting whole schema.
79
+ * Values can be Partial<JsonSchema> or StandardSchema (per-field Zod).
80
+ */
81
+ fields?: Record<string, FieldDefOrSchema>;
82
+ /** Annotations forwarded to WebMCP (readOnlyHint, etc) — extensible */
83
+ annotations?: WebMCPAnnotations;
84
+ /** Scope — global registers immediately, scoped requires useWebMCP, manual requires explicit register() */
85
+ scope?: 'global' | 'scoped' | 'manual';
86
+ /** Alias for scope='global' */
87
+ global?: boolean;
88
+ /** Enable/disable registration (default true) */
89
+ enabled?: boolean;
90
+ /** If true, ambiguous inference throws instead of warn */
91
+ strict?: boolean;
92
+ };
93
+ type WebMCPTool<F extends (...args: any) => any> = F & {
94
+ readonly __webmcpBrand: true;
95
+ readonly tool: ToolContract;
96
+ readonly definition: ToolContract;
97
+ /** Async register — returns unregister fn */
98
+ register: (opts?: {
99
+ signal?: AbortSignal;
100
+ }) => Promise<() => void>;
101
+ /** Unregister (sync convenience) */
102
+ unregister: () => void;
103
+ /** Current status */
104
+ readonly status: RegistrationStatus;
105
+ /** Promise for pending registration */
106
+ readonly registration: Promise<void> | null;
107
+ isRegistered: () => boolean;
108
+ readonly __fn: F;
109
+ readonly __standardSchema?: StandardSchemaV1;
110
+ readonly __outputStandardSchema?: StandardSchemaV1;
111
+ };
112
+ type RegistrationStatus = 'unregistered' | 'registering' | 'registered' | 'unregistering' | 'error';
113
+ type ModelContextLike = {
114
+ registerTool: (def: {
115
+ name: string;
116
+ description?: string;
117
+ inputSchema?: JsonSchema;
118
+ outputSchema?: JsonSchema;
119
+ annotations?: WebMCPAnnotations;
120
+ execute: (args: unknown, ctx?: unknown) => unknown;
121
+ }, opts?: {
122
+ signal?: AbortSignal;
123
+ }) => Promise<void>;
124
+ };
125
+
126
+ export type { FieldDefOrSchema as F, JsonSchema as J, ModelContextLike as M, RegistrationStatus as R, StandardSchemaV1 as S, ToolContract as T, WebMCPOptions as W, WebMCPTool as a, FieldDef as b, WebMCPAnnotations as c };
package/dist/zod.cjs ADDED
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ // src/internal/schema.ts
6
+ var ZOD_CONVERTER_KEY = "__simpleWebmcp_zodConverter";
7
+ function registerZodConverter(fn) {
8
+ globalThis[ZOD_CONVERTER_KEY] = fn;
9
+ }
10
+
11
+ // src/zod.ts
12
+ function convertZodDef(z) {
13
+ const def = z._def ?? z.def ?? z._zod?.def ?? null;
14
+ const typeName = def?.typeName ?? z._zod?.traits?.type ?? z.typeName ?? void 0;
15
+ const ctor = z.constructor?.name || "";
16
+ if (typeName === "ZodOptional" || ctor === "ZodOptional") {
17
+ const inner = z.unwrap?.() ?? def?.innerType ?? def?.type ?? null;
18
+ if (inner) return convertZodDef(inner);
19
+ }
20
+ if (typeName === "ZodDefault" || ctor === "ZodDefault") {
21
+ const inner = def?.innerType ?? z._def?.innerType ?? null;
22
+ const json = inner ? convertZodDef(inner) : { type: "string" };
23
+ const defVal = typeof def?.defaultValue === "function" ? def.defaultValue() : def?.defaultValue;
24
+ if (defVal !== void 0) json.default = defVal;
25
+ return json;
26
+ }
27
+ if (typeName === "ZodNullable" || ctor === "ZodNullable") {
28
+ const inner = def?.innerType ?? null;
29
+ const json = inner ? convertZodDef(inner) : { type: "string" };
30
+ return json;
31
+ }
32
+ if (typeName === "ZodEffects" || ctor === "ZodEffects") {
33
+ const inner = def?.schema ?? null;
34
+ if (inner) return convertZodDef(inner);
35
+ }
36
+ if (typeName === "ZodString" || ctor === "ZodString") {
37
+ const j = { type: "string" };
38
+ if (def?.checks) {
39
+ for (const c of def.checks) {
40
+ if (c.kind === "min") j.minLength = c.value;
41
+ if (c.kind === "max") j.maxLength = c.value;
42
+ if (c.kind === "regex") j.pattern = c.regex?.source;
43
+ if (c.kind === "email") j.format = "email";
44
+ if (c.kind === "url") j.format = "uri";
45
+ if (c.kind === "uuid") j.format = "uuid";
46
+ }
47
+ }
48
+ if (z.description) j.description = z.description;
49
+ else if (def?.description) j.description = def.description;
50
+ return j;
51
+ }
52
+ if (typeName === "ZodNumber" || ctor === "ZodNumber") {
53
+ const j = { type: "number" };
54
+ if (def?.checks) {
55
+ for (const c of def.checks) {
56
+ if (c.kind === "min") j.minimum = c.value;
57
+ if (c.kind === "max") j.maximum = c.value;
58
+ if (c.kind === "int") j.type = "integer";
59
+ }
60
+ }
61
+ if (z.description) j.description = z.description;
62
+ return j;
63
+ }
64
+ if (typeName === "ZodBoolean" || ctor === "ZodBoolean") return { type: "boolean" };
65
+ if (typeName === "ZodBigInt" || ctor === "ZodBigInt") return { type: "integer" };
66
+ if (typeName === "ZodDate" || ctor === "ZodDate") return { type: "string", format: "date-time" };
67
+ if (typeName === "ZodEnum" || ctor === "ZodEnum") {
68
+ const values = def?.values ?? z._def?.values ?? z.options ?? [];
69
+ return { type: "string", enum: values };
70
+ }
71
+ if (typeName === "ZodNativeEnum" || ctor === "ZodNativeEnum") {
72
+ const values = def?.values ? Object.values(def.values) : [];
73
+ return { type: "string", enum: values };
74
+ }
75
+ if (typeName === "ZodLiteral" || ctor === "ZodLiteral") {
76
+ const val = def?.value ?? z.value;
77
+ return { enum: [val] };
78
+ }
79
+ if (typeName === "ZodArray" || ctor === "ZodArray") {
80
+ const inner = def?.type ?? def?.element ?? null;
81
+ const items = inner ? convertZodDef(inner) : { type: "string" };
82
+ return { type: "array", items };
83
+ }
84
+ if (typeName === "ZodObject" || ctor === "ZodObject") {
85
+ let shape;
86
+ try {
87
+ shape = typeof z.shape === "object" ? z.shape : def?.shape?.() ?? def?.shape ?? z._def?.shape?.() ?? void 0;
88
+ if (typeof shape === "function") shape = shape();
89
+ } catch {
90
+ }
91
+ if (!shape) shape = def?.shape ?? {};
92
+ const properties = {};
93
+ const required = [];
94
+ for (const [k, v] of Object.entries(shape)) {
95
+ const propJson = convertZodDef(v);
96
+ properties[k] = propJson;
97
+ const isOpt = v?._def?.typeName === "ZodOptional" || v?.isOptional?.() === true;
98
+ if (!isOpt) required.push(k);
99
+ const desc = v?.description ?? v?._def?.description;
100
+ if (desc && !properties[k].description) properties[k].description = desc;
101
+ }
102
+ const j = { type: "object", properties, required: required.length ? required : void 0, additionalProperties: false };
103
+ return j;
104
+ }
105
+ if (typeName === "ZodRecord" || ctor === "ZodRecord") {
106
+ return { type: "object", additionalProperties: true };
107
+ }
108
+ if (typeName === "ZodUnion" || ctor === "ZodUnion") {
109
+ const opts = def?.options ?? def?.types ?? [];
110
+ return { anyOf: opts.map((o) => convertZodDef(o)) };
111
+ }
112
+ if (typeName === "ZodDiscriminatedUnion" || ctor === "ZodDiscriminatedUnion") {
113
+ const opts = def?.options ?? [];
114
+ return { anyOf: opts.map((o) => convertZodDef(o)) };
115
+ }
116
+ if (typeName === "ZodIntersection" || ctor === "ZodIntersection") {
117
+ const left = def?.left ?? null;
118
+ const right = def?.right ?? null;
119
+ const all = [left, right].filter(Boolean).map((o) => convertZodDef(o));
120
+ return { allOf: all };
121
+ }
122
+ return { type: "string" };
123
+ }
124
+ function zodLikeToJsonSchema(schema) {
125
+ const anyS = schema;
126
+ if (!anyS || typeof anyS !== "object") return null;
127
+ const hasZodMarker = "_def" in anyS || "_zod" in anyS;
128
+ if (!hasZodMarker) return null;
129
+ try {
130
+ return convertZodDef(anyS);
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+ registerZodConverter(zodLikeToJsonSchema);
136
+ var zodToJsonSchema = zodLikeToJsonSchema;
137
+ var zod_default = zodLikeToJsonSchema;
138
+
139
+ exports.convertZodDef = convertZodDef;
140
+ exports.default = zod_default;
141
+ exports.zodLikeToJsonSchema = zodLikeToJsonSchema;
142
+ exports.zodToJsonSchema = zodToJsonSchema;
143
+ //# sourceMappingURL=zod.cjs.map
144
+ //# sourceMappingURL=zod.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/schema.ts","../src/zod.ts"],"names":[],"mappings":"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","file":"zod.cjs","sourcesContent":["import type { JsonSchema, StandardSchemaV1, FieldDefOrSchema } from '../types.js';\nimport { isStandardSchema, looksLikeJsonSchema } from './utils.js';\n\n// Global registry key for optional Zod converter (split to keep core lean, no hard dep)\nconst ZOD_CONVERTER_KEY = '__simpleWebmcp_zodConverter';\n\ntype ZodConverter = (schema: unknown) => JsonSchema | null;\n\nexport function registerZodConverter(fn: ZodConverter): void {\n (globalThis as any)[ZOD_CONVERTER_KEY] = fn;\n}\n\nfunction getZodConverter(): ZodConverter | null {\n return (globalThis as any)[ZOD_CONVERTER_KEY] ?? null;\n}\n\nfunction zodLikeToJsonSchema(schema: unknown): JsonSchema | null {\n const conv = getZodConverter();\n if (conv) {\n try {\n return conv(schema);\n } catch {\n return null;\n }\n }\n return null;\n}\n\n/**\n * Convert a StandardSchema (maybe Zod) to JSON Schema for WebMCP registration.\n * If conversion fails, returns null → caller uses fallback.\n * Core knows only JSON; Zod support is provided via `import 'simple-webmcp/zod'` which registers converter.\n */\nexport function standardSchemaToJsonSchema(schema: StandardSchemaV1): JsonSchema | null {\n const zodJson = zodLikeToJsonSchema(schema as unknown);\n if (zodJson) return zodJson;\n return null;\n}\n\n/**\n * Convert any `schema` option (JSON Schema or StandardSchema) to JSON Schema.\n * Returns { json, standard } where standard is kept for runtime validation if needed.\n */\nexport function normalizeSchemaInput(\n schema: JsonSchema | StandardSchemaV1 | undefined | null,\n): { json: JsonSchema | null; standard: StandardSchemaV1 | null } {\n if (!schema) return { json: null, standard: null };\n if (looksLikeJsonSchema(schema)) {\n return { json: schema as JsonSchema, standard: null };\n }\n if (isStandardSchema(schema)) {\n const std = schema as StandardSchemaV1;\n const json = standardSchemaToJsonSchema(std);\n return { json, standard: std };\n }\n // If it's a plain object that looks like Zod but without ~standard (older Zod), try convert via registered converter\n const maybeZodJson = zodLikeToJsonSchema(schema as unknown);\n if (maybeZodJson) return { json: maybeZodJson, standard: isStandardSchema(schema) ? (schema as StandardSchemaV1) : null };\n // Unknown — treat as JSON if it has properties, else null\n if (typeof schema === 'object' && schema !== null) {\n // Could be JSON schema without explicit type — treat as json\n return { json: schema as unknown as JsonSchema, standard: null };\n }\n return { json: null, standard: null };\n}\n\n/**\n * Convert a field definition (Partial<JsonSchema> | StandardSchema) to JsonSchema fragment.\n */\nexport function fieldDefToJsonSchema(field: FieldDefOrSchema): JsonSchema {\n if (!field) return {};\n if (isStandardSchema(field)) {\n const json = standardSchemaToJsonSchema(field as StandardSchemaV1);\n if (json) return json;\n // Fallback: unknown standard field → placeholder object\n return { type: 'string' };\n }\n // It's a FieldDef (Partial<JsonSchema>)\n return field as JsonSchema;\n}\n\n/**\n * Merge `fields` patch onto base schema per corrected hierarchy:\n * base = schema (whole) or inferred; fields decorates it.\n */\nexport function applyFieldsPatch(base: JsonSchema, fields?: Record<string, FieldDefOrSchema>): JsonSchema {\n if (!fields || Object.keys(fields).length === 0) return base;\n const result: JsonSchema = {\n ...base,\n properties: { ...(base.properties || {}) },\n required: [...(base.required || [])],\n };\n if (!result.properties) result.properties = {};\n if (!result.required) result.required = [];\n\n for (const [key, field] of Object.entries(fields)) {\n const isStd = isStandardSchema(field);\n if (isStd) {\n const json = standardSchemaToJsonSchema(field as StandardSchemaV1);\n const fragment = json ?? { type: 'string' };\n result.properties[key] = fragment;\n const anyField = field as any;\n const isOpt = anyField?._def?.typeName === 'ZodOptional' || anyField?.isOptional?.() === true;\n if (!isOpt) {\n if (!result.required.includes(key)) result.required.push(key);\n } else {\n result.required = result.required.filter((k) => k !== key);\n }\n const desc = anyField?.description ?? anyField?._def?.description;\n if (desc && !(result.properties[key] as any).description) (result.properties[key] as any).description = desc;\n } else {\n const patch = field as JsonSchema;\n const existing = (result.properties[key] as JsonSchema) || {};\n const merged: JsonSchema = { ...existing, ...patch };\n result.properties[key] = merged;\n if ('required' in patch) {\n const req = (patch as any).required;\n if (req === false) {\n result.required = result.required.filter((k) => k !== key);\n delete (merged as any).required;\n } else if (req === true) {\n if (!result.required.includes(key)) result.required.push(key);\n delete (merged as any).required;\n }\n } else {\n if (!(key in (base.properties || {})) && !(patch as any).required) {\n const hasDefault = 'default' in patch;\n if (!hasDefault && !result.required.includes(key)) result.required.push(key);\n }\n }\n }\n }\n\n if (result.required && result.required.length === 0) delete (result as any).required;\n return result;\n}\n\n/**\n * Build final inputSchema per hierarchy:\n * 1. whole schema (schema) if json available → base\n * 2. else inferred schema\n * 3. then fields patch\n */\nexport function buildFinalInputSchema(opts: {\n wholeSchema?: JsonSchema | StandardSchemaV1;\n inferred: JsonSchema;\n fields?: Record<string, FieldDefOrSchema>;\n}): { json: JsonSchema; standard: StandardSchemaV1 | null } {\n const wholeNorm = normalizeSchemaInput(opts.wholeSchema);\n let baseJson: JsonSchema;\n let standard: StandardSchemaV1 | null = wholeNorm.standard;\n\n if (wholeNorm.json) {\n baseJson = wholeNorm.json;\n } else if (wholeNorm.standard && !wholeNorm.json) {\n baseJson = opts.inferred;\n } else {\n baseJson = opts.inferred;\n }\n\n if (!baseJson.type) baseJson.type = 'object';\n if (!baseJson.properties) baseJson.properties = {};\n if (baseJson.additionalProperties == null) baseJson.additionalProperties = false;\n\n const patched = applyFieldsPatch(baseJson, opts.fields);\n\n return { json: patched, standard };\n}\n\n/**\n * Normalize outputSchema similarly but without fields\n */\nexport function normalizeOutputSchema(schema?: JsonSchema | StandardSchemaV1): { json?: JsonSchema; standard?: StandardSchemaV1 } {\n if (!schema) return {};\n const norm = normalizeSchemaInput(schema);\n if (norm.json) return { json: norm.json, standard: norm.standard ?? undefined };\n if (norm.standard) return { standard: norm.standard };\n return {};\n}\n","/**\n * simple-webmcp/zod — optional Zod → JSON Schema converter\n * Importing this registers a global converter so `webmcp(fn, {schema: z.object(...)})`\n * and per-field `fields: {query: z.string()}` just work without manual conversion.\n *\n * Usage:\n * import 'simple-webmcp/zod'; // side-effect — enables Zod in core\n * import { zodToJsonSchema } from 'simple-webmcp/zod'; // or manual convert\n *\n * Keep this separate from core to hit 3KB gz target (core no Zod).\n */\n\nimport type { JsonSchema } from './types.js';\nimport { registerZodConverter } from './internal/schema.js';\n\nfunction convertZodDef(z: any): JsonSchema {\n const def = z._def ?? z.def ?? z._zod?.def ?? null;\n const typeName: string | undefined = def?.typeName ?? z._zod?.traits?.type ?? z.typeName ?? undefined;\n const ctor = z.constructor?.name || '';\n\n if (typeName === 'ZodOptional' || ctor === 'ZodOptional') {\n const inner = z.unwrap?.() ?? def?.innerType ?? def?.type ?? null;\n if (inner) return convertZodDef(inner);\n }\n if (typeName === 'ZodDefault' || ctor === 'ZodDefault') {\n const inner = def?.innerType ?? z._def?.innerType ?? null;\n const json = inner ? convertZodDef(inner) : ({ type: 'string' } as JsonSchema);\n const defVal = typeof def?.defaultValue === 'function' ? def.defaultValue() : def?.defaultValue;\n if (defVal !== undefined) (json as any).default = defVal;\n return json;\n }\n if (typeName === 'ZodNullable' || ctor === 'ZodNullable') {\n const inner = def?.innerType ?? null;\n const json = inner ? convertZodDef(inner) : ({ type: 'string' } as JsonSchema);\n return json;\n }\n if (typeName === 'ZodEffects' || ctor === 'ZodEffects') {\n const inner = def?.schema ?? null;\n if (inner) return convertZodDef(inner);\n }\n\n if (typeName === 'ZodString' || ctor === 'ZodString') {\n const j: JsonSchema = { type: 'string' };\n if (def?.checks) {\n for (const c of def.checks) {\n if (c.kind === 'min') j.minLength = c.value;\n if (c.kind === 'max') j.maxLength = c.value;\n if (c.kind === 'regex') j.pattern = c.regex?.source;\n if (c.kind === 'email') j.format = 'email';\n if (c.kind === 'url') j.format = 'uri';\n if (c.kind === 'uuid') j.format = 'uuid';\n }\n }\n if (z.description) j.description = z.description;\n else if (def?.description) j.description = def.description;\n return j;\n }\n if (typeName === 'ZodNumber' || ctor === 'ZodNumber') {\n const j: JsonSchema = { type: 'number' };\n if (def?.checks) {\n for (const c of def.checks) {\n if (c.kind === 'min') j.minimum = c.value;\n if (c.kind === 'max') j.maximum = c.value;\n if (c.kind === 'int') j.type = 'integer';\n }\n }\n if (z.description) j.description = z.description;\n return j;\n }\n if (typeName === 'ZodBoolean' || ctor === 'ZodBoolean') return { type: 'boolean' };\n if (typeName === 'ZodBigInt' || ctor === 'ZodBigInt') return { type: 'integer' };\n if (typeName === 'ZodDate' || ctor === 'ZodDate') return { type: 'string', format: 'date-time' };\n if (typeName === 'ZodEnum' || ctor === 'ZodEnum') {\n const values = def?.values ?? z._def?.values ?? z.options ?? [];\n return { type: 'string', enum: values };\n }\n if (typeName === 'ZodNativeEnum' || ctor === 'ZodNativeEnum') {\n const values = def?.values ? Object.values(def.values) : [];\n return { type: 'string', enum: values };\n }\n if (typeName === 'ZodLiteral' || ctor === 'ZodLiteral') {\n const val = def?.value ?? z.value;\n return { enum: [val] };\n }\n if (typeName === 'ZodArray' || ctor === 'ZodArray') {\n const inner = def?.type ?? def?.element ?? null;\n const items = inner ? convertZodDef(inner) : ({ type: 'string' } as JsonSchema);\n return { type: 'array', items };\n }\n if (typeName === 'ZodObject' || ctor === 'ZodObject') {\n let shape: Record<string, any> | undefined;\n try {\n shape = typeof z.shape === 'object' ? z.shape : def?.shape?.() ?? def?.shape ?? z._def?.shape?.() ?? undefined;\n if (typeof shape === 'function') shape = shape();\n } catch {}\n if (!shape) shape = def?.shape ?? {};\n const properties: Record<string, JsonSchema> = {};\n const required: string[] = [];\n for (const [k, v] of Object.entries(shape as Record<string, any>)) {\n const propJson = convertZodDef(v);\n properties[k] = propJson;\n const isOpt = (v as any)?._def?.typeName === 'ZodOptional' || (v as any)?.isOptional?.() === true;\n if (!isOpt) required.push(k);\n const desc = (v as any)?.description ?? (v as any)?._def?.description;\n if (desc && !properties[k].description) properties[k].description = desc;\n }\n const j: JsonSchema = { type: 'object', properties, required: required.length ? required : undefined, additionalProperties: false };\n return j;\n }\n if (typeName === 'ZodRecord' || ctor === 'ZodRecord') {\n return { type: 'object', additionalProperties: true };\n }\n if (typeName === 'ZodUnion' || ctor === 'ZodUnion') {\n const opts = def?.options ?? def?.types ?? [];\n return { anyOf: (opts as any[]).map((o) => convertZodDef(o)) };\n }\n if (typeName === 'ZodDiscriminatedUnion' || ctor === 'ZodDiscriminatedUnion') {\n const opts = def?.options ?? [];\n return { anyOf: (opts as any[]).map((o) => convertZodDef(o)) };\n }\n if (typeName === 'ZodIntersection' || ctor === 'ZodIntersection') {\n const left = def?.left ?? null;\n const right = def?.right ?? null;\n const all = [left, right].filter(Boolean).map((o) => convertZodDef(o));\n return { allOf: all };\n }\n\n return { type: 'string' };\n}\n\nexport function zodLikeToJsonSchema(schema: unknown): JsonSchema | null {\n const anyS = schema as any;\n if (!anyS || typeof anyS !== 'object') return null;\n const hasZodMarker = '_def' in anyS || '_zod' in anyS;\n if (!hasZodMarker) return null;\n try {\n return convertZodDef(anyS);\n } catch {\n return null;\n }\n}\n\n// Register globally so core `standardSchemaToJsonSchema` picks it up\nregisterZodConverter(zodLikeToJsonSchema);\n\n// Also export helpers for manual use\nexport { convertZodDef };\nexport const zodToJsonSchema = zodLikeToJsonSchema;\nexport default zodLikeToJsonSchema;\n"]}
package/dist/zod.d.cts ADDED
@@ -0,0 +1,20 @@
1
+ import { J as JsonSchema } from './types-D-cwSfEU.cjs';
2
+
3
+ /**
4
+ * simple-webmcp/zod — optional Zod → JSON Schema converter
5
+ * Importing this registers a global converter so `webmcp(fn, {schema: z.object(...)})`
6
+ * and per-field `fields: {query: z.string()}` just work without manual conversion.
7
+ *
8
+ * Usage:
9
+ * import 'simple-webmcp/zod'; // side-effect — enables Zod in core
10
+ * import { zodToJsonSchema } from 'simple-webmcp/zod'; // or manual convert
11
+ *
12
+ * Keep this separate from core to hit 3KB gz target (core no Zod).
13
+ */
14
+
15
+ declare function convertZodDef(z: any): JsonSchema;
16
+ declare function zodLikeToJsonSchema(schema: unknown): JsonSchema | null;
17
+
18
+ declare const zodToJsonSchema: typeof zodLikeToJsonSchema;
19
+
20
+ export { convertZodDef, zodLikeToJsonSchema as default, zodLikeToJsonSchema, zodToJsonSchema };
package/dist/zod.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { J as JsonSchema } from './types-D-cwSfEU.js';
2
+
3
+ /**
4
+ * simple-webmcp/zod — optional Zod → JSON Schema converter
5
+ * Importing this registers a global converter so `webmcp(fn, {schema: z.object(...)})`
6
+ * and per-field `fields: {query: z.string()}` just work without manual conversion.
7
+ *
8
+ * Usage:
9
+ * import 'simple-webmcp/zod'; // side-effect — enables Zod in core
10
+ * import { zodToJsonSchema } from 'simple-webmcp/zod'; // or manual convert
11
+ *
12
+ * Keep this separate from core to hit 3KB gz target (core no Zod).
13
+ */
14
+
15
+ declare function convertZodDef(z: any): JsonSchema;
16
+ declare function zodLikeToJsonSchema(schema: unknown): JsonSchema | null;
17
+
18
+ declare const zodToJsonSchema: typeof zodLikeToJsonSchema;
19
+
20
+ export { convertZodDef, zodLikeToJsonSchema as default, zodLikeToJsonSchema, zodToJsonSchema };