@feniix/bridgekit 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to `@feniix/bridgekit` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.9.4] - 2026-05-27
8
+
9
+ ### Fixed
10
+
11
+ - `isObjectSchema` now short-circuits on `$ref` before checking
12
+ `type === "object"`, closing a hole where a hybrid `{type: "object", $ref:
13
+ "..."}` schema would bypass the construction-time `$ref` rejection
14
+ introduced in 0.9.3. The hybrid shape is not produced by TypeBox today;
15
+ the fix guards against future schema producers (including hand-crafted
16
+ JSON Schema). Existing `Type.Object` (no `$ref`), `Type.Cyclic` (`$ref`
17
+ without `type`), and `Type.Intersect` (`allOf` at root, no `$ref`)
18
+ behavior is unchanged. Resolves [#51](https://github.com/feniix/bridgekit/issues/51).
19
+
20
+ ## [0.9.3] - 2026-05-27
21
+
22
+ ### Fixed
23
+
24
+ - `createMcpServer` construction errors now recognize top-level `$ref` /
25
+ `Type.Cyclic` / `Type.Recursive` schemas as a distinct rejection class with
26
+ `$ref`-specific guidance (inline the referenced shape or split recursive
27
+ shapes into multiple non-recursive tools), instead of falling through to
28
+ the misdirecting "wrap with `Type.Object`" recipe. New stable error code
29
+ `BRIDGEKIT_MCP_REF_PARAMETERS` for consumer branching. Existing
30
+ `BRIDGEKIT_MCP_NON_OBJECT_PARAMETERS` and `BRIDGEKIT_MCP_DUPLICATE_TOOL_NAME`
31
+ codes are unchanged. Resolves [#44](https://github.com/feniix/bridgekit/issues/44).
32
+
7
33
  ## [0.9.2] - 2026-05-26
8
34
 
9
35
  ### Fixed
@@ -24,6 +24,12 @@ function isObjectSchema(schema) {
24
24
  if (typeof schema !== "object" || schema === null)
25
25
  return false;
26
26
  const candidate = schema;
27
+ // $ref takes structural precedence: a hybrid {type: "object", $ref: "..."}
28
+ // is not an inlined object schema — it's a reference, and
29
+ // assertObjectShapedParameters owns the rejection recipe. Return false so
30
+ // the $ref branch runs.
31
+ if (typeof candidate.$ref === "string")
32
+ return false;
27
33
  if (candidate.type === "object")
28
34
  return true;
29
35
  if (Array.isArray(candidate.allOf) && candidate.allOf.length > 0) {
@@ -37,11 +43,19 @@ function isObjectSchema(schema) {
37
43
  * branches and surface the first non-object branch by index — a bare `"allOf"`
38
44
  * label is misleading because the rejection is owned by one specific branch,
39
45
  * not the composition itself.
46
+ *
47
+ * The `$ref` check is first because `Type.Cyclic` produces
48
+ * `{ $defs: {...}, $ref: "..." }` at the root; the `$ref` is the load-bearing
49
+ * structural signal regardless of what else is set, and the recipe for that
50
+ * shape (inline or split) is different from the generic `Type.Object(...)`
51
+ * wrap recipe.
40
52
  */
41
53
  function schemaTypeLabel(schema) {
42
54
  if (typeof schema !== "object" || schema === null)
43
55
  return "unknown";
44
56
  const candidate = schema;
57
+ if (typeof candidate.$ref === "string")
58
+ return "$ref";
45
59
  if (typeof candidate.type === "string")
46
60
  return candidate.type;
47
61
  if (Array.isArray(candidate.anyOf))
@@ -83,6 +97,7 @@ function toInputSchema(parameters) {
83
97
  * recipe-shaped and may evolve; the code is part of the public contract).
84
98
  */
85
99
  const ERROR_CODE_NON_OBJECT_PARAMETERS = "BRIDGEKIT_MCP_NON_OBJECT_PARAMETERS";
100
+ const ERROR_CODE_REF_PARAMETERS = "BRIDGEKIT_MCP_REF_PARAMETERS";
86
101
  const ERROR_CODE_DUPLICATE_TOOL_NAME = "BRIDGEKIT_MCP_DUPLICATE_TOOL_NAME";
87
102
  function throwWithCode(message, code) {
88
103
  const error = new Error(message);
@@ -93,6 +108,19 @@ function assertObjectShapedParameters(tools) {
93
108
  for (const tool of tools) {
94
109
  if (!isObjectSchema(tool.parameters)) {
95
110
  const typeLabel = schemaTypeLabel(tool.parameters);
111
+ // Top-level $ref (TypeBox's `Type.Cyclic` lowering, or a bare `Type.Ref`)
112
+ // gets its own branch and code. The generic "wrap with Type.Object(...)"
113
+ // recipe is the wrong fix for a recursive schema — the user wants to
114
+ // express recursion, not wrap a primitive — so a $ref shape needs
115
+ // $ref-specific guidance (inline or split) before the generic branch
116
+ // can run.
117
+ if (typeLabel === "$ref") {
118
+ throwWithCode(`createMcpServer: tool "${tool.name}" has a top-level $ref schema (type="$ref"). ` +
119
+ "Top-level $ref / Type.Cyclic schemas are not currently supported by the MCP wire layer " +
120
+ "because tools/list ships inputSchema by value and the SDK client does not resolve $refs. " +
121
+ "Inline the referenced schema (wrap the target shape directly with Type.Object(...)) " +
122
+ "or split recursive shapes into multiple non-recursive tools.", ERROR_CODE_REF_PARAMETERS);
123
+ }
96
124
  let message = `createMcpServer: tool "${tool.name}" has a non-object parameters schema (type="${typeLabel}"). ` +
97
125
  "MCP requires Type.Object(...) at the top level; use Type.Object({ value: Type.String() }) " +
98
126
  "to wrap a single-field schema, or Type.Intersect([Type.Object(...), Type.Object(...)]) for " +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feniix/bridgekit",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "BridgeKit defines TypeBox-backed tools once and adapts them to pi, MCP, and other hosts.",
5
5
  "keywords": [
6
6
  "pi",