@zackbart/connecta 0.2.1 → 0.4.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 (65) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/dist/catalog.d.ts.map +1 -1
  3. package/dist/catalog.js +62 -0
  4. package/dist/catalog.js.map +1 -1
  5. package/dist/connectors/api.d.ts +14 -0
  6. package/dist/connectors/api.d.ts.map +1 -1
  7. package/dist/connectors/api.js +18 -1
  8. package/dist/connectors/api.js.map +1 -1
  9. package/dist/connectors/remote-mcp.d.ts.map +1 -1
  10. package/dist/connectors/remote-mcp.js +22 -8
  11. package/dist/connectors/remote-mcp.js.map +1 -1
  12. package/dist/credentials.d.ts.map +1 -1
  13. package/dist/credentials.js +4 -1
  14. package/dist/credentials.js.map +1 -1
  15. package/dist/errors.d.ts +53 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +89 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/execute.d.ts.map +1 -1
  20. package/dist/execute.js +10 -6
  21. package/dist/execute.js.map +1 -1
  22. package/dist/index.d.ts +20 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/json-schema.d.ts +3 -0
  27. package/dist/json-schema.d.ts.map +1 -0
  28. package/dist/json-schema.js +6 -0
  29. package/dist/json-schema.js.map +1 -0
  30. package/dist/meta-tools.d.ts +31 -2
  31. package/dist/meta-tools.d.ts.map +1 -1
  32. package/dist/meta-tools.js +75 -31
  33. package/dist/meta-tools.js.map +1 -1
  34. package/dist/node.d.ts +1 -0
  35. package/dist/node.d.ts.map +1 -1
  36. package/dist/node.js.map +1 -1
  37. package/dist/server.d.ts +2 -0
  38. package/dist/server.d.ts.map +1 -1
  39. package/dist/server.js +5 -1
  40. package/dist/server.js.map +1 -1
  41. package/dist/storage/file.d.ts +6 -2
  42. package/dist/storage/file.d.ts.map +1 -1
  43. package/dist/storage/file.js +3 -2
  44. package/dist/storage/file.js.map +1 -1
  45. package/dist/validate.d.ts +39 -0
  46. package/dist/validate.d.ts.map +1 -0
  47. package/dist/validate.js +66 -0
  48. package/dist/validate.js.map +1 -0
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. package/package.json +8 -1
  52. package/src/catalog.ts +61 -0
  53. package/src/connectors/api.ts +25 -1
  54. package/src/connectors/remote-mcp.ts +32 -13
  55. package/src/credentials.ts +4 -1
  56. package/src/errors.ts +126 -0
  57. package/src/execute.ts +13 -6
  58. package/src/index.ts +24 -0
  59. package/src/json-schema.ts +11 -0
  60. package/src/meta-tools.ts +102 -46
  61. package/src/node.ts +1 -0
  62. package/src/server.ts +7 -1
  63. package/src/storage/file.ts +12 -3
  64. package/src/validate.ts +96 -0
  65. package/src/version.ts +1 -1
package/src/catalog.ts CHANGED
@@ -79,6 +79,41 @@ function refName(ref: string): string {
79
79
  return ref.split("/").pop() ?? ref;
80
80
  }
81
81
 
82
+ /**
83
+ * Whether a schema declares anything renderSchema knows how to render on its
84
+ * own. Used to decide if the non-allOf half of a schema is worth rendering:
85
+ * without this, a plain `{ allOf: [...] }` would render its (empty) local half
86
+ * through the raw-JSON fallback and emit `{} & …`.
87
+ */
88
+ function declaresShape(s: Record<string, unknown>): boolean {
89
+ return (
90
+ typeof s.$ref === "string" ||
91
+ Array.isArray(s.oneOf) ||
92
+ Array.isArray(s.anyOf) ||
93
+ Array.isArray(s.enum) ||
94
+ s.const !== undefined ||
95
+ s.items !== undefined ||
96
+ s.properties !== undefined ||
97
+ s.type !== undefined
98
+ );
99
+ }
100
+
101
+ /**
102
+ * Parenthesize a top-level union so it doesn't read as part of a surrounding
103
+ * `&`. Only separators outside braces count, so a nested union or a property
104
+ * description containing a pipe doesn't trigger stray parentheses.
105
+ */
106
+ function grouped(part: string): string {
107
+ let nesting = 0;
108
+ for (let i = 0; i < part.length; i += 1) {
109
+ const char = part[i];
110
+ if (char === "{" || char === "(" || char === "[") nesting += 1;
111
+ else if (char === "}" || char === ")" || char === "]") nesting -= 1;
112
+ else if (nesting === 0 && part.startsWith(" | ", i)) return `(${part})`;
113
+ }
114
+ return part;
115
+ }
116
+
82
117
  function renderSchema(
83
118
  schema: unknown,
84
119
  defs: Record<string, unknown>,
@@ -91,6 +126,27 @@ function renderSchema(
91
126
  }
92
127
  const s = schema as Record<string, unknown>;
93
128
 
129
+ // allOf composes rather than replaces: it is checked before every other
130
+ // keyword, and renders the schema's own shape alongside its members instead
131
+ // of returning early. A schema carrying both allOf and properties (the usual
132
+ // OpenAPI-derived "extend this base" shape, and equally legal with $ref,
133
+ // enum, const, or items) would otherwise silently drop whichever half lost
134
+ // the branch race. The schema's own shape comes first, being the more
135
+ // specific half, and is rendered at the current depth because its members
136
+ // sit at this nesting level, not one below.
137
+ if (Array.isArray(s.allOf)) {
138
+ const { allOf: _members, ...own } = s;
139
+ const parts = declaresShape(own)
140
+ ? [renderSchema(own, defs, seen, depth)]
141
+ : [];
142
+ for (const member of s.allOf) {
143
+ parts.push(renderSchema(member, defs, seen, depth + 1));
144
+ }
145
+ if (parts.length === 0) return "unknown";
146
+ if (parts.length === 1) return parts[0] as string;
147
+ return parts.map(grouped).join(" & ");
148
+ }
149
+
94
150
  if (typeof s.$ref === "string") {
95
151
  const name = refName(s.$ref);
96
152
  if (seen.has(name)) return name;
@@ -112,6 +168,11 @@ function renderSchema(
112
168
  if (Array.isArray(s.enum)) {
113
169
  return s.enum.map((value) => JSON.stringify(value)).join(" | ");
114
170
  }
171
+ // Checked before type/properties so a discriminator like
172
+ // { type: "string", const: "emoji" } renders as "emoji" rather than string.
173
+ // JSON.stringify(undefined) returns undefined (not a string), so an explicit
174
+ // `const: undefined` must fall through to the regular type rendering.
175
+ if (s.const !== undefined) return JSON.stringify(s.const);
115
176
 
116
177
  const type = s.type;
117
178
  if (type === "array" || s.items) {
@@ -1,3 +1,4 @@
1
+ import { validateToolInput } from "../validate.js";
1
2
  import type {
2
3
  Connector,
3
4
  ConnectorCredentialConfig,
@@ -40,6 +41,14 @@ export interface ApiOptions {
40
41
  values: ConnectorCredentialValues,
41
42
  ctx: ConnectorContext,
42
43
  ) => Promise<CredentialTestResult>;
44
+ /**
45
+ * Validate call arguments against each tool's `inputSchema` before invoking
46
+ * the handler (default true). Mismatches fail with a non-retryable
47
+ * `invalid_args` ConnectorCallError instead of reaching the handler. Set
48
+ * false to restore the pre-validation pass-through for deployments relying
49
+ * on loose coercion.
50
+ */
51
+ validateArgs?: boolean;
43
52
  tools: ApiTool[];
44
53
  }
45
54
 
@@ -47,6 +56,12 @@ export interface ApiOptions {
47
56
  * A connector defined entirely in code: static tool defs + fetch handlers.
48
57
  * Tool inputs are plain JSON Schema objects (bring your own zod-to-json-schema
49
58
  * conversion if you prefer zod). call_tool JSON-wraps the handler's return.
59
+ *
60
+ * Arguments are validated against `inputSchema` before the handler runs
61
+ * (disable with `validateArgs: false`). This is deliberately asymmetric with
62
+ * remote MCP connectors, which stay pass-through: the downstream server is
63
+ * authoritative for its own schemas, and re-validating with our JSON Schema
64
+ * draft/format semantics could reject calls the downstream would accept.
50
65
  */
51
66
  export function api(id: string, opts: ApiOptions): Connector {
52
67
  const defs: ToolDef[] = opts.tools.map((t) => ({
@@ -57,6 +72,7 @@ export function api(id: string, opts: ApiOptions): Connector {
57
72
  annotations: t.annotations,
58
73
  }));
59
74
  const byName = new Map(opts.tools.map((t) => [t.name, t]));
75
+ const validateArgs = opts.validateArgs ?? true;
60
76
  return {
61
77
  id,
62
78
  title: opts.title,
@@ -74,7 +90,15 @@ export function api(id: string, opts: ApiOptions): Connector {
74
90
  if (!tool) {
75
91
  throw new Error(`Unknown tool "${name}" on connector "${id}"`);
76
92
  }
77
- return tool.handler(args ?? {}, ctx);
93
+ const input = args ?? {};
94
+ if (validateArgs && tool.inputSchema) {
95
+ const invalid = validateToolInput(tool.inputSchema, input, {
96
+ address: `${id}.${name}`,
97
+ logger: ctx.logger,
98
+ });
99
+ if (invalid) throw invalid;
100
+ }
101
+ return tool.handler(input, ctx);
78
102
  },
79
103
  };
80
104
  }
@@ -4,6 +4,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
4
4
  import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
5
5
  import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker";
6
6
  import { KvOAuthProvider } from "../auth/downstream-oauth.js";
7
+ import { ConnectorCallError } from "../errors.js";
7
8
  import { CONNECTA_VERSION } from "../version.js";
8
9
  import type {
9
10
  Connector,
@@ -60,6 +61,14 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
60
61
  const states = new WeakMap<object, ConnectionState>();
61
62
  const isOauth = opts.auth?.type === "oauth";
62
63
 
64
+ /** Typed per-call auth signal; the SDK's UnauthorizedError stays as cause. */
65
+ const authRequiredError = (cause: unknown) =>
66
+ new ConnectorCallError(
67
+ "auth_required",
68
+ `Connector "${id}" requires authorization — call authorize_connector({ connector: "${id}" }) and open the returned URL.`,
69
+ { cause },
70
+ );
71
+
63
72
  const stateFor = (ctx: ConnectorContext): ConnectionState => {
64
73
  const scope = ctx.requestScope ?? ctx;
65
74
  let state = states.get(scope);
@@ -176,6 +185,7 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
176
185
  // "auth_required".
177
186
  if (err instanceof UnauthorizedError) {
178
187
  state.authRequired = true;
188
+ throw authRequiredError(err);
179
189
  }
180
190
  throw err;
181
191
  } finally {
@@ -209,19 +219,28 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
209
219
  async callTool(name, args, ctx) {
210
220
  const state = stateFor(ctx);
211
221
  await ensureConnected(ctx, state);
212
- return state.client!.callTool(
213
- {
214
- name,
215
- arguments: (args ?? {}) as Record<string, unknown>,
216
- },
217
- undefined,
218
- ctx.timeoutMs || ctx.signal
219
- ? {
220
- ...(ctx.timeoutMs ? { timeout: ctx.timeoutMs } : {}),
221
- ...(ctx.signal ? { signal: ctx.signal } : {}),
222
- }
223
- : undefined,
224
- );
222
+ try {
223
+ return await state.client!.callTool(
224
+ {
225
+ name,
226
+ arguments: (args ?? {}) as Record<string, unknown>,
227
+ },
228
+ undefined,
229
+ ctx.timeoutMs || ctx.signal
230
+ ? {
231
+ ...(ctx.timeoutMs ? { timeout: ctx.timeoutMs } : {}),
232
+ ...(ctx.signal ? { signal: ctx.signal } : {}),
233
+ }
234
+ : undefined,
235
+ );
236
+ } catch (err) {
237
+ // A grant revoked after connect surfaces here, not in ensureConnected.
238
+ if (err instanceof UnauthorizedError) {
239
+ state.authRequired = true;
240
+ throw authRequiredError(err);
241
+ }
242
+ throw err;
243
+ }
225
244
  },
226
245
 
227
246
  async status(ctx): Promise<ConnectorStatus> {
@@ -219,7 +219,10 @@ export class CredentialVault {
219
219
  value: string,
220
220
  updatedBy: string,
221
221
  ): Promise<CredentialMetadata> {
222
- return this.setAll(connectorId, { value }, updatedBy);
222
+ // `await` (not a bare promise return) so a validation throw inside setAll
223
+ // never sits handler-less for the thenable-adoption microtask — workerd
224
+ // reports that gap as an unhandled rejection.
225
+ return await this.setAll(connectorId, { value }, updatedBy);
223
226
  }
224
227
 
225
228
  async setAll(
package/src/errors.ts ADDED
@@ -0,0 +1,126 @@
1
+ // Typed failure contract for connector tool calls. Web-API only — no node:
2
+ // imports here.
3
+
4
+ /** Machine-readable classification of a failed connector tool call. */
5
+ export type ConnectorCallErrorCode =
6
+ | "timeout"
7
+ | "auth_required"
8
+ | "rate_limited"
9
+ | "unavailable"
10
+ | "invalid_args"
11
+ | "connector_call_failed";
12
+
13
+ const RETRYABLE_BY_CODE: Record<ConnectorCallErrorCode, boolean> = {
14
+ timeout: true,
15
+ rate_limited: true,
16
+ unavailable: true,
17
+ auth_required: false,
18
+ invalid_args: false,
19
+ connector_call_failed: false,
20
+ };
21
+
22
+ /** Non-negative integer milliseconds, or undefined for anything else. */
23
+ function normalizeRetryAfterMs(value: number | undefined): number | undefined {
24
+ if (value === undefined) return undefined;
25
+ if (!Number.isFinite(value) || value < 0) return undefined;
26
+ return Math.trunc(value);
27
+ }
28
+
29
+ /**
30
+ * Throw from `Connector.callTool` (or anything beneath it) to classify a
31
+ * failure exactly. Untyped errors fall back to a message-text heuristic, so a
32
+ * connector whose legitimate error text mentions "timeout" is misread as a
33
+ * retryable timeout — this class is the escape hatch. `retryable` defaults per
34
+ * code (timeout, rate_limited, and unavailable retry; the rest do not) and may
35
+ * be overridden.
36
+ *
37
+ * `retryAfterMs` carries a wait window the connector already knows — a
38
+ * `Retry-After` header, say — so the engine can wait that long instead of
39
+ * guessing, and so an agent that receives the failure can decide when to
40
+ * re-issue.
41
+ */
42
+ export class ConnectorCallError extends Error {
43
+ readonly code: ConnectorCallErrorCode;
44
+ readonly retryable: boolean;
45
+ /**
46
+ * Connector-known wait window in ms before this call is worth repeating,
47
+ * or undefined when the connector reported none. Always an own property —
48
+ * under ES2022 class fields the declaration itself defines it, so guarding
49
+ * the assignment would not keep it off the instance. Keeping the window out
50
+ * of the wire format is `classifyCallError`'s job, not this constructor's.
51
+ */
52
+ readonly retryAfterMs?: number;
53
+
54
+ constructor(
55
+ code: ConnectorCallErrorCode,
56
+ message: string,
57
+ opts: { retryable?: boolean; retryAfterMs?: number; cause?: unknown } = {},
58
+ ) {
59
+ super(
60
+ message,
61
+ opts.cause !== undefined ? { cause: opts.cause } : undefined,
62
+ );
63
+ this.name = "ConnectorCallError";
64
+ this.code = code;
65
+ this.retryable = opts.retryable ?? RETRYABLE_BY_CODE[code];
66
+ this.retryAfterMs = normalizeRetryAfterMs(opts.retryAfterMs);
67
+ }
68
+ }
69
+
70
+ /** The `error` object surfaced in call_tool/batch_call value-mode results. */
71
+ export interface CallErrorDetails {
72
+ code: string;
73
+ message: string;
74
+ retryable: boolean;
75
+ /**
76
+ * Connector-reported wait window in ms, when known. Reported verbatim — the
77
+ * engine bounds how long it will itself wait, but the caller sees the real
78
+ * window so it can schedule a re-issue.
79
+ */
80
+ retryAfterMs?: number;
81
+ }
82
+
83
+ const RETRYABLE_MESSAGE_RE =
84
+ /timeout|timed out|econnreset|econnrefused|temporar|rate.?limit|429|502|503|504|refcountedcanceler|different request/i;
85
+ const TIMEOUT_MESSAGE_RE = /timed out|timeout/i;
86
+
87
+ /** Message-text fallback used when an error carries no typed classification. */
88
+ export function messageLooksRetryable(message: string): boolean {
89
+ return RETRYABLE_MESSAGE_RE.test(message);
90
+ }
91
+
92
+ /**
93
+ * Classify a value thrown by a connector call. A `ConnectorCallError` is
94
+ * authoritative; anything else falls back to the historical message-text
95
+ * heuristic.
96
+ */
97
+ export function classifyCallError(
98
+ err: unknown,
99
+ fallbackCode = "connector_call_failed",
100
+ ): CallErrorDetails {
101
+ if (err instanceof ConnectorCallError) {
102
+ return {
103
+ code: err.code,
104
+ message: err.message,
105
+ retryable: err.retryable,
106
+ ...(err.retryAfterMs !== undefined
107
+ ? { retryAfterMs: err.retryAfterMs }
108
+ : {}),
109
+ };
110
+ }
111
+ // An aborted fetch rejects with a DOMException named "AbortError" whose
112
+ // message ("The operation was aborted", and variants across runtimes) matches
113
+ // neither heuristic below — so a call the engine itself cancelled would read
114
+ // as a non-retryable failure, the opposite of the truth. Note this also
115
+ // covers an abort the connector triggered for its own reasons; running out of
116
+ // time is by far the likelier cause and retryable/timeout is the safer read.
117
+ if (err instanceof Error && err.name === "AbortError") {
118
+ return { code: "timeout", message: err.message, retryable: true };
119
+ }
120
+ const message = err instanceof Error ? err.message : String(err);
121
+ return {
122
+ code: TIMEOUT_MESSAGE_RE.test(message) ? "timeout" : fallbackCode,
123
+ message,
124
+ retryable: RETRYABLE_MESSAGE_RE.test(message),
125
+ };
126
+ }
package/src/execute.ts CHANGED
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { compactSchema, rankTools, summarizeDescription } from "./catalog.js";
4
4
  import { recordToolActivity, type ActivityRequestContext } from "./activity.js";
5
5
  import { errorResult, jsonResult, type ToolResult } from "./meta-tools.js";
6
+ import { classifyCallError, ConnectorCallError } from "./errors.js";
6
7
  import { unwrapMcpResult } from "./mcp-result.js";
7
8
  import type { Registry } from "./registry.js";
8
9
  import type {
@@ -191,7 +192,10 @@ export async function buildSandboxProviders(
191
192
  try {
192
193
  timer = setTimeout(() => {
193
194
  controller.abort(
194
- new Error(`Tool call timed out after ${hostCallTimeoutMs}ms`),
195
+ new ConnectorCallError(
196
+ "timeout",
197
+ `Tool call timed out after ${hostCallTimeoutMs}ms`,
198
+ ),
195
199
  );
196
200
  }, hostCallTimeoutMs);
197
201
  const pending = resolved.connector.callTool(
@@ -216,16 +220,16 @@ export async function buildSandboxProviders(
216
220
  return value;
217
221
  } catch (err) {
218
222
  registry.recordFailure(resolved.connector.id, Date.now() - started, err);
219
- const timedOut = /timed out|timeout/i.test(msg(err));
223
+ const details = classifyCallError(err);
220
224
  recordToolActivity(activity, {
221
225
  connectorId: resolved.connector.id,
222
226
  toolName: resolved.toolName,
223
227
  address: `${resolved.connector.id}.${resolved.toolName}`,
224
228
  source: "execute_code",
225
- outcome: timedOut ? "timeout" : "error",
229
+ outcome: details.code === "timeout" ? "timeout" : "error",
226
230
  durationMs: Date.now() - started,
227
231
  attempts: 1,
228
- errorCode: timedOut ? "timeout" : "connector_call_failed",
232
+ errorCode: details.code,
229
233
  });
230
234
  throw err;
231
235
  } finally {
@@ -269,8 +273,11 @@ export async function buildSandboxProviders(
269
273
  );
270
274
  continue;
271
275
  }
276
+ // `await` (not a bare promise return) so a synchronous throw inside
277
+ // callAddress never sits handler-less for the thenable-adoption
278
+ // microtask — workerd reports that gap as an unhandled rejection.
272
279
  fns[key] = async (args: unknown) =>
273
- callAddress(`${connector.id}.${t.name}`, args);
280
+ await callAddress(`${connector.id}.${t.name}`, args);
274
281
  }
275
282
  if (Object.keys(fns).length > 0) {
276
283
  providers.push({ name: ns, fns });
@@ -288,7 +295,7 @@ export async function buildSandboxProviders(
288
295
  `connecta.batch accepts at most ${EXECUTE_MAX_BATCH_CALLS} calls`,
289
296
  );
290
297
  }
291
- return Promise.all(
298
+ return await Promise.all(
292
299
  calls.map(async (call) => {
293
300
  const item = call as { address?: unknown; args?: unknown };
294
301
  try {
package/src/index.ts CHANGED
@@ -61,6 +61,22 @@ export interface ConnectaConfig {
61
61
  * stash the full text for get_result paging. Default 50_000.
62
62
  */
63
63
  maxResultBytes?: number;
64
+ /**
65
+ * Deadline (ms) applied to call_tool/batch_call calls that pass no
66
+ * `timeoutMs`, giving the connector both a budget (`ctx.timeoutMs`) and a
67
+ * cancellation signal (`ctx.signal`). An explicit per-call `timeoutMs` always
68
+ * wins. **Opt-in — undefined by default**, because switching it on globally
69
+ * would put a deadline on every call in an existing deployment and the
70
+ * failure mode is a working long-running call starting to time out.
71
+ * `execute_code` host calls are unaffected; they already carry a 15 s bound.
72
+ *
73
+ * Bounds a single attempt, not the whole call — the same as an explicit
74
+ * `timeoutMs` has always done. A call that also passes `maxRetries` can
75
+ * therefore run to roughly `(maxRetries + 1)` times this value plus backoff.
76
+ * `maxRetries` defaults to 0, so this is the total for every call that does
77
+ * not explicitly ask to retry.
78
+ */
79
+ defaultToolTimeoutMs?: number;
64
80
  serverInfo?: {
65
81
  name?: string;
66
82
  version?: string;
@@ -141,6 +157,7 @@ export function createConnecta(config: ConnectaConfig): Connecta {
141
157
  activityReadGate: config.activityReadGate,
142
158
  activityDeploymentId: config.activityDeploymentId,
143
159
  executor: config.executor,
160
+ defaultToolTimeoutMs: config.defaultToolTimeoutMs,
144
161
  credentialVault,
145
162
  deploymentInfo: config.deploymentInfo,
146
163
  branding: config.branding,
@@ -159,6 +176,13 @@ export function createConnecta(config: ConnectaConfig): Connecta {
159
176
 
160
177
  export { remoteMcp } from "./connectors/remote-mcp.js";
161
178
  export { api } from "./connectors/api.js";
179
+ export { ConnectorCallError } from "./errors.js";
180
+ export type { ConnectorCallErrorCode, CallErrorDetails } from "./errors.js";
181
+ // The same argument validation api() performs, usable by connectors that
182
+ // implement the Connector interface directly. Returns the error rather than
183
+ // throwing so the caller decides what to do with it.
184
+ export { validateToolInput } from "./validate.js";
185
+ export type { ValidateToolInputOptions } from "./validate.js";
162
186
  export { bearerToken } from "./auth/bearer.js";
163
187
  export { memoryStorage } from "./storage/memory.js";
164
188
  export { CONNECTA_VERSION } from "./version.js";
@@ -0,0 +1,11 @@
1
+ // Public re-export of the JSON Schema validator connecta itself uses, so
2
+ // downstream code that validates at build time (a manifest generator asserting
3
+ // its own output, say) resolves the same implementation and version through an
4
+ // explicit subpath rather than through npm hoisting.
5
+ export { Validator } from "@cfworker/json-schema";
6
+ export type {
7
+ OutputUnit,
8
+ Schema,
9
+ SchemaDraft,
10
+ ValidationResult,
11
+ } from "@cfworker/json-schema";