@abide/abide 0.110.0 → 0.110.2

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
@@ -1,5 +1,17 @@
1
1
  # @abide/abide
2
2
 
3
+ ## 0.110.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 14ee265: an SVG element spelled in camelCase hydrates, the compiler asking for the tag the parser writes inside an <svg> ([`f10c81b`](https://github.com/briancray/abide/commit/f10c81bff3ce0e43762a71e24634a218312c0d93))
8
+
9
+ ## 0.110.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 2aac4eb: an MCP tool's inputSchema is an object at its top level, and args that can never be one refuse the app at boot ([`ec9d80b`](https://github.com/briancray/abide/commit/ec9d80bb6f8902ba1d9f016f6dcd85eba83a47ef))
14
+
3
15
  ## 0.110.0
4
16
 
5
17
  ### Minor Changes
package/docs/REGISTRY.md CHANGED
@@ -313,7 +313,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
313
313
 
314
314
  | Name | Signature | Meaning | Rules |
315
315
  | --- | --- | --- | --- |
316
- | `/__abide/mcp` | `POST` | The streamable-http MCP endpoint. | 19.21, 19.22, 20.5, 20.7, 20.8, 20.9, 20.10, 20.12, 20.13, 20.14, 20.16, 20.17, 36.7, 16.78 |
316
+ | `/__abide/mcp` | `POST` | The streamable-http MCP endpoint. | 19.21, 19.22, 20.5, 20.7, 20.8, 20.9, 20.10, 20.12, 20.13, 20.14, 20.16, 20.17, 20.19, 20.20, 36.7, 16.78 |
317
317
 
318
318
  # Assistant
319
319
 
@@ -325,7 +325,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
325
325
  | `AssistantOptions` | `{ mcp?: boolean; maxTurns?: number }` | What `assistant` takes beside a provider. | 44.6, 44.8, 44.11 |
326
326
  | `Provider` | `{ run(messages: AssistantMessage[], tools: Toolbox, limits: { maxTurns: number; signal: AbortSignal }): AsyncGenerator<AssistantEvent> }` | What runs a conversation. | 44.2, 44.29 |
327
327
  | `Toolbox` | `{ list: ToolDefinition[]; call(name: string, input: unknown): Promise<{ text: string; failed: boolean }> }` | The app's MCP tools, called as the asker. | 44.6, 44.7 |
328
- | `ToolDefinition` | `{ name: string; description: string; inputSchema: unknown; annotations: { readOnlyHint: boolean; destructiveHint: boolean; idempotentHint: boolean }; outputSchema: unknown }` | A tool as the app's MCP surface lists it. | 20.14, 20.16, 44.6 |
328
+ | `ToolDefinition` | `{ name: string; description: string; inputSchema: unknown; annotations: { readOnlyHint: boolean; destructiveHint: boolean; idempotentHint: boolean }; outputSchema: unknown }` | A tool as the app's MCP surface lists it. | 20.14, 20.16, 20.19, 44.6 |
329
329
 
330
330
  ## `claudeApi`
331
331
 
package/docs/RULEBOOK.md CHANGED
@@ -1320,6 +1320,12 @@ tool, the status it was answered with and how long it took, whether `/__abide/mc
1320
1320
  made the call — a call naming a tool it does not publish answered 404, and one whose answer threw
1321
1321
  500.
1322
1322
 
1323
+ 20.19 A tool `/__abide/mcp` lists MUST declare an `inputSchema` whose top-level `type` is `object`,
1324
+ whatever its handler's args schema declares there.
1325
+
1326
+ 20.20 A handler `/__abide/mcp` would list whose args schema can never be an object MUST refuse the
1327
+ app at boot, naming the handler.
1328
+
1323
1329
  # 21. Generated headers
1324
1330
 
1325
1331
  21.1 Every response MUST carry `x-content-type-options: nosniff`.
package/docs/abide.md CHANGED
@@ -7661,7 +7661,7 @@ Generated surfaces: what is written in place rather than imported.
7661
7661
 
7662
7662
  | Name | Signature | Meaning | Rules |
7663
7663
  | --- | --- | --- | --- |
7664
- | `/__abide/mcp` | `POST` | The streamable-http MCP endpoint. | [19.21](/rulebook#19.21), [19.22](/rulebook#19.22), [20.5](/rulebook#20.5), [20.7](/rulebook#20.7), [20.8](/rulebook#20.8), [20.9](/rulebook#20.9), [20.10](/rulebook#20.10), [20.12](/rulebook#20.12), [20.13](/rulebook#20.13), [20.14](/rulebook#20.14), [20.16](/rulebook#20.16), [20.17](/rulebook#20.17), [36.7](/rulebook#36.7), [16.78](/rulebook#16.78) |
7664
+ | `/__abide/mcp` | `POST` | The streamable-http MCP endpoint. | [19.21](/rulebook#19.21), [19.22](/rulebook#19.22), [20.5](/rulebook#20.5), [20.7](/rulebook#20.7), [20.8](/rulebook#20.8), [20.9](/rulebook#20.9), [20.10](/rulebook#20.10), [20.12](/rulebook#20.12), [20.13](/rulebook#20.13), [20.14](/rulebook#20.14), [20.16](/rulebook#20.16), [20.17](/rulebook#20.17), [20.19](/rulebook#20.19), [20.20](/rulebook#20.20), [36.7](/rulebook#36.7), [16.78](/rulebook#16.78) |
7665
7665
 
7666
7666
  ## Taught in
7667
7667
 
@@ -7787,7 +7787,7 @@ Generated surfaces: what is written in place rather than imported.
7787
7787
 
7788
7788
  | Name | Signature | Meaning | Rules |
7789
7789
  | --- | --- | --- | --- |
7790
- | `ToolDefinition` | `{ name: string; description: string; inputSchema: unknown; annotations: { readOnlyHint: boolean; destructiveHint: boolean; idempotentHint: boolean }; outputSchema: unknown }` | A tool as the app's MCP surface lists it. | [20.14](/rulebook#20.14), [20.16](/rulebook#20.16), [44.6](/rulebook#44.6) |
7790
+ | `ToolDefinition` | `{ name: string; description: string; inputSchema: unknown; annotations: { readOnlyHint: boolean; destructiveHint: boolean; idempotentHint: boolean }; outputSchema: unknown }` | A tool as the app's MCP surface lists it. | [20.14](/rulebook#20.14), [20.16](/rulebook#20.16), [20.19](/rulebook#20.19), [44.6](/rulebook#44.6) |
7791
7791
 
7792
7792
  ## Taught in
7793
7793
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abide/abide",
3
- "version": "0.110.0",
3
+ "version": "0.110.2",
4
4
  "type": "module",
5
5
  "description": "Isomorphic type-safe framework for reactive async interfaces for humans and machines",
6
6
  "license": "MIT",
@@ -81,7 +81,8 @@ export type PlacedNode = {
81
81
  */
82
82
  parent: string
83
83
  /**
84
- * An element's FOLDED tag, for the kind check `elementHere` makes. '' for the other two kinds.
84
+ * An element's tag as the parser writes it, for the kind check `elementHere` makes: folded,
85
+ * then given back SVG's camelCase (`SVG_CASE`). '' for the other two kinds.
85
86
  */
86
87
  tag: string
87
88
  /**
@@ -115,6 +116,51 @@ export type PlacedNode = {
115
116
  children: PlacedNode[]
116
117
  }
117
118
 
119
+ // THE NAMES THE PARSER GIVES BACK SVG'S CASE, folded to the case it writes — the HTML standard's
120
+ // adjustment table for foreign content. None is an HTML element, so one stands under an `<svg>`
121
+ // even where the compiler cannot see it: a component's root does not know where it is invoked.
122
+ const SVG_CASE = new Map<string, string>()
123
+ for (const name of [
124
+ 'altGlyph',
125
+ 'altGlyphDef',
126
+ 'altGlyphItem',
127
+ 'animateColor',
128
+ 'animateMotion',
129
+ 'animateTransform',
130
+ 'clipPath',
131
+ 'feBlend',
132
+ 'feColorMatrix',
133
+ 'feComponentTransfer',
134
+ 'feComposite',
135
+ 'feConvolveMatrix',
136
+ 'feDiffuseLighting',
137
+ 'feDisplacementMap',
138
+ 'feDistantLight',
139
+ 'feDropShadow',
140
+ 'feFlood',
141
+ 'feFuncA',
142
+ 'feFuncB',
143
+ 'feFuncG',
144
+ 'feFuncR',
145
+ 'feGaussianBlur',
146
+ 'feImage',
147
+ 'feMerge',
148
+ 'feMergeNode',
149
+ 'feMorphology',
150
+ 'feOffset',
151
+ 'fePointLight',
152
+ 'feSpecularLighting',
153
+ 'feSpotLight',
154
+ 'feTile',
155
+ 'feTurbulence',
156
+ 'foreignObject',
157
+ 'glyphRef',
158
+ 'linearGradient',
159
+ 'radialGradient',
160
+ 'textPath',
161
+ ])
162
+ SVG_CASE.set(name.toLowerCase(), name)
163
+
118
164
  /** A run of siblings being walked: the region's own root, or the inside of one element. */
119
165
  type Frame = {
120
166
  /**
@@ -202,7 +248,13 @@ export class Cursor {
202
248
  * emitted between its open and close tags and nowhere else. A void element is left at once.
203
249
  */
204
250
  element(node: ElementNode): PlacedNode {
205
- const at = this.record('element', node, node.lowerTag, false, [])
251
+ const at = this.record(
252
+ 'element',
253
+ node,
254
+ SVG_CASE.get(node.lowerTag) ?? node.lowerTag,
255
+ false,
256
+ [],
257
+ )
206
258
  this.frames.push({
207
259
  parent: at.name,
208
260
  within: at,
@@ -253,6 +253,14 @@ export class McpSurface {
253
253
  handlers: Map<string, Mounted>,
254
254
  answer: (request: Request) => Promise<Response>,
255
255
  ) {
256
+ // 20.20 — a tool's arguments are an object, so args that can never be one are no tool.
257
+ for (const mounted of handlers.values()) {
258
+ const tool = surfaceOf(mounted, 'mcp')
259
+ if (tool !== undefined && toolInput(tool.schema) === undefined)
260
+ throw new Error(
261
+ `abide: ${tool.name}'s args can never be an object, which is all an MCP tool is called with — give it object args, or \`clients: { mcp: false }\`. RULEBOOK 20.20.`,
262
+ )
263
+ }
256
264
  this.#handlers = handlers
257
265
  this.#answer = answer
258
266
  }
@@ -280,7 +288,7 @@ export class McpSurface {
280
288
  listing.push({
281
289
  name: tool.name,
282
290
  description: tool.description ?? '',
283
- inputSchema: tool.schema ?? { type: 'object' },
291
+ inputSchema: toolInput(tool.schema),
284
292
  // 20.14 — what the method promises: a `GET` is safe, and a `GET`, a `PUT` and a
285
293
  // `DELETE` idempotent. Any other write may destroy, and nothing says it does not.
286
294
  annotations: {
@@ -411,6 +419,32 @@ function logCall(
411
419
  )
412
420
  }
413
421
 
422
+ /**
423
+ * `schema` as a tool's `inputSchema`, its top-level `type` `object` whatever it declared there, or
424
+ * `undefined` where no value it admits is an object. 20.19, 20.20.
425
+ */
426
+ function toolInput(schema: unknown): object | undefined {
427
+ if (schema === undefined || schema === true) return { type: 'object' }
428
+ if (!admitsObject(schema)) return undefined
429
+ return { ...(schema as object), type: 'object' }
430
+ }
431
+
432
+ /** Whether a JSON Schema admits some object — its `type`, and each of `anyOf`, `oneOf` and `allOf`. */
433
+ function admitsObject(schema: unknown): boolean {
434
+ if (schema === true) return true
435
+ if (schema === null || typeof schema !== 'object') return false
436
+ const { type, anyOf, oneOf, allOf } = schema as Record<string, unknown>
437
+ if (
438
+ type !== undefined &&
439
+ type !== 'object' &&
440
+ !(Array.isArray(type) && type.includes('object'))
441
+ )
442
+ return false
443
+ if (Array.isArray(anyOf) && !anyOf.some(admitsObject)) return false
444
+ if (Array.isArray(oneOf) && !oneOf.some(admitsObject)) return false
445
+ return !Array.isArray(allOf) || allOf.every(admitsObject)
446
+ }
447
+
414
448
  /**
415
449
  * Whether a tool declares an `outputSchema`: MCP's structured content is an object, so only a
416
450
  * result schema that is one. 20.16.