@mcp-native/a2ui 0.0.3 → 0.2.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/README.md +110 -13
- package/dist/binding.d.ts +33 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +83 -0
- package/dist/binding.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +26 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +157 -37
- package/dist/index.js.map +1 -1
- package/dist/mime.d.ts +6 -0
- package/dist/mime.d.ts.map +1 -0
- package/dist/mime.js +2 -0
- package/dist/mime.js.map +1 -0
- package/dist/schema-revision.d.ts +3 -0
- package/dist/schema-revision.d.ts.map +1 -0
- package/dist/schema-revision.js +3 -0
- package/dist/schema-revision.js.map +1 -0
- package/dist/v1/format-string.d.ts +8 -0
- package/dist/v1/format-string.d.ts.map +1 -0
- package/dist/v1/format-string.js +377 -0
- package/dist/v1/format-string.js.map +1 -0
- package/dist/v1/index.d.ts +9 -0
- package/dist/v1/index.d.ts.map +1 -0
- package/dist/v1/index.js +6 -0
- package/dist/v1/index.js.map +1 -0
- package/dist/v1/parse.d.ts +12 -0
- package/dist/v1/parse.d.ts.map +1 -0
- package/dist/v1/parse.js +96 -0
- package/dist/v1/parse.js.map +1 -0
- package/dist/v1/resolve.d.ts +19 -0
- package/dist/v1/resolve.d.ts.map +1 -0
- package/dist/v1/resolve.js +112 -0
- package/dist/v1/resolve.js.map +1 -0
- package/dist/v1/schemas.d.ts +7 -0
- package/dist/v1/schemas.d.ts.map +1 -0
- package/dist/v1/schemas.js +92 -0
- package/dist/v1/schemas.js.map +1 -0
- package/dist/v1/store.d.ts +21 -0
- package/dist/v1/store.d.ts.map +1 -0
- package/dist/v1/store.js +301 -0
- package/dist/v1/store.js.map +1 -0
- package/dist/v1/types.d.ts +58 -0
- package/dist/v1/types.d.ts.map +1 -0
- package/dist/v1/types.js +10 -0
- package/dist/v1/types.js.map +1 -0
- package/dist/v1/validate.d.ts +26 -0
- package/dist/v1/validate.d.ts.map +1 -0
- package/dist/v1/validate.js +434 -0
- package/dist/v1/validate.js.map +1 -0
- package/dist/v1/vendor/agent_to_renderer.json +216 -0
- package/dist/v1/vendor/catalog.json +1346 -0
- package/dist/v1/vendor/common_types.json +395 -0
- package/package.json +5 -2
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/CHECKSUMS.sha256 +13 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/PROVENANCE.md +12 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/catalogs/basic/catalog.json +1346 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/agent_capabilities.json +26 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/agent_to_renderer.json +216 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/agent_to_renderer_list.json +10 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/agent_to_renderer_list_wrapper.json +14 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/catalog_definition.json +267 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/common_types.json +395 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/renderer_capabilities.json +29 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/renderer_data_model.json +22 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/renderer_to_agent.json +161 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/renderer_to_agent_list.json +10 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/renderer_to_agent_list_wrapper.json +14 -0
- package/schemas/7541f953050cd58b80f0bf5d85fe2d63192af305/json/sample.json +22 -0
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
[](https://www.npmjs.com/package/@mcp-native/a2ui)
|
|
9
9
|
[](https://github.com/pablospaniard/mcp-native/blob/main/LICENSE)
|
|
10
10
|
|
|
11
|
-
[GitHub](https://github.com/pablospaniard/mcp-native) · [Architecture](https://github.com/pablospaniard/mcp-native/blob/main/docs/RFC-0001-architecture.md) · [Security](https://github.com/pablospaniard/mcp-native/blob/main/SECURITY.md)
|
|
11
|
+
[GitHub](https://github.com/pablospaniard/mcp-native) · [Architecture](https://github.com/pablospaniard/mcp-native/blob/main/docs/RFC-0001-architecture.md) · [Standards status](https://github.com/pablospaniard/mcp-native/blob/main/docs/standards-compatibility.md) · [Security](https://github.com/pablospaniard/mcp-native/blob/main/SECURITY.md)
|
|
12
12
|
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
|
-
> **Experimental:** this package implements MCP Native's deliberately small `0.1` proof-of-concept surface.
|
|
15
|
+
> **Experimental:** this package implements MCP Native's deliberately small internal `0.1` proof-of-concept surface. `0.1` is not an A2UI protocol version, and this package does not currently claim A2UI v1.0 compatibility.
|
|
16
16
|
|
|
17
|
-
`@mcp-native/a2ui` parses untrusted JSON or JavaScript values into a validated, typed surface before a host renders anything. Unknown versions, node types, action types, and invalid JSON values fail
|
|
17
|
+
`@mcp-native/a2ui` resolves explicitly typed resource links and parses untrusted JSON or JavaScript values into a validated, typed surface before a host renders anything. Unknown or ambiguous resources fail with `A2uiResourceError`; unknown versions, node types, action types, non-plain objects, oversized trees, and invalid JSON values fail with `A2uiParseError`. Surfaces are capped at `A2UI_MAX_DEPTH` (32) and `A2UI_MAX_NODES` (256).
|
|
18
18
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
@@ -65,6 +65,81 @@ const surface = parseA2uiSurface(
|
|
|
65
65
|
);
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
## Resolve a tool-result resource
|
|
69
|
+
|
|
70
|
+
An MCP tool may return a `resource_link` for a declarative surface. Pass the connected runtime or any `A2uiResourceReader` to the resolver:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { resolveA2uiResourceFromToolResult } from "@mcp-native/a2ui";
|
|
74
|
+
|
|
75
|
+
const toolResult = await runtime.callTool("open_profile");
|
|
76
|
+
const resolved = await resolveA2uiResourceFromToolResult(runtime, toolResult);
|
|
77
|
+
|
|
78
|
+
console.log(resolved.uri);
|
|
79
|
+
console.log(resolved.surface.root);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Resolution succeeds only when:
|
|
83
|
+
|
|
84
|
+
1. the tool result is not marked as an error;
|
|
85
|
+
2. it contains exactly one `resource_link` with MIME type `application/a2ui+json`;
|
|
86
|
+
3. `resources/read` returns exactly one item with the same URI and MIME type;
|
|
87
|
+
4. that item contains text, not a blob;
|
|
88
|
+
5. the text passes `parseA2uiSurface`.
|
|
89
|
+
|
|
90
|
+
Other tool content and non-A2UI resource links may coexist with the surface link. MCP Native never guesses a MIME type or chooses between multiple matching surfaces.
|
|
91
|
+
|
|
92
|
+
The prototype's `application/a2ui+json` resource convention comes from earlier A2UI-over-MCP work. The [A2UI v1.0 Candidate protocol](https://github.com/a2ui-project/a2ui/blob/7541f953050cd58b80f0bf5d85fe2d63192af305/specification/v1_0/docs/a2ui_protocol.md) is transport-agnostic and uses a stream of `v1.0` envelopes. Recognizing this media type does not establish v1.0 conformance.
|
|
93
|
+
|
|
94
|
+
The official v1.0 surface-store state now has a strict static adapter into the internal trusted render plan. This does not evolve the custom `0.1` object into a competing wire protocol. See the [compatibility matrix and conformance roadmap](https://github.com/pablospaniard/mcp-native/blob/main/docs/standards-compatibility.md).
|
|
95
|
+
|
|
96
|
+
## A2UI-over-MCP capability binding
|
|
97
|
+
|
|
98
|
+
The package exports an experimental project-owned binding under `io.github.pablospaniard/mcp-native-a2ui`. It is enabled only when both peers advertise the exact binding version, A2UI Candidate revision, JSONL resource transport, and MIME type:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { A2UI_MCP_EXTENSION_CAPABILITIES, negotiateA2uiMcpBinding } from "@mcp-native/a2ui";
|
|
102
|
+
|
|
103
|
+
const result = negotiateA2uiMcpBinding(
|
|
104
|
+
adapter.getClientExtensionSettings(),
|
|
105
|
+
adapter.getServerExtensionSettings(),
|
|
106
|
+
);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A fallback result means the host uses ordinary MCP text or structured data. A resource link, MIME type, or `_meta` value never activates the binding by itself. The exact capability exchange, ordered `resource-text-jsonl` mapping, and failure behavior are documented in the [project binding contract](https://github.com/pablospaniard/mcp-native/blob/main/docs/a2ui-mcp-binding.md).
|
|
110
|
+
|
|
111
|
+
## Official v1.0 envelopes and surface store
|
|
112
|
+
|
|
113
|
+
After mutual negotiation, hosts can resolve a JSONL resource and apply lifecycle envelopes:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import {
|
|
117
|
+
A2UI_MCP_EXTENSION_CAPABILITIES,
|
|
118
|
+
A2uiSurfaceStore,
|
|
119
|
+
createA2uiV1BasicCatalogPolicy,
|
|
120
|
+
negotiateA2uiMcpBinding,
|
|
121
|
+
resolveA2uiV1JsonlFromToolResult,
|
|
122
|
+
} from "@mcp-native/a2ui";
|
|
123
|
+
|
|
124
|
+
const binding = negotiateA2uiMcpBinding(
|
|
125
|
+
A2UI_MCP_EXTENSION_CAPABILITIES,
|
|
126
|
+
adapter.getServerExtensionSettings(),
|
|
127
|
+
);
|
|
128
|
+
const { envelopes } = await resolveA2uiV1JsonlFromToolResult(runtime, toolResult, binding);
|
|
129
|
+
const store = new A2uiSurfaceStore();
|
|
130
|
+
store.applyAll(envelopes);
|
|
131
|
+
|
|
132
|
+
const surface = store.getValidated(
|
|
133
|
+
"surface-id",
|
|
134
|
+
createA2uiV1BasicCatalogPolicy({
|
|
135
|
+
allowedComponentNames: ["Column", "Text", "Button"],
|
|
136
|
+
allowedEventNames: ["continue"],
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Only `createSurface`, `updateComponents`, `updateDataModel`, and `deleteSurface` envelopes are accepted in this milestone. Raw store snapshots may be incomplete while ordered updates arrive. The store bounds retained surfaces and components; `getValidated` is the required pre-render boundary for the pinned basic catalog, explicit host component/event/function allowlists, reachable child references and cycles, template-aware binding paths, and component placement rules. The React Native package adapts the supported static subset after revalidation. Function-call envelopes, dynamic templates, mounted v1 interactions, and renderer-to-agent messages remain deferred. The custom `0.1` resolver is unchanged and never receives a failed v1 stream.
|
|
142
|
+
|
|
68
143
|
## Supported surface
|
|
69
144
|
|
|
70
145
|
| Node | Required fields | Purpose |
|
|
@@ -78,25 +153,47 @@ The only supported action is `{ type: "tool", name, arguments? }`. Arguments mus
|
|
|
78
153
|
|
|
79
154
|
## Public API
|
|
80
155
|
|
|
81
|
-
| Export
|
|
82
|
-
|
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
|
|
|
156
|
+
| Export | Purpose |
|
|
157
|
+
| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
158
|
+
| `resolveA2uiResourceFromToolResult` | Reads and parses the single explicit A2UI link in a tool result. |
|
|
159
|
+
| `parseA2uiSurface` | Validates input and returns a typed `A2uiSurface`. |
|
|
160
|
+
| `A2uiResourceError`, `A2uiParseError` | Specific resolution and parsing failures. |
|
|
161
|
+
| `A2UI_MIME_TYPE`, `A2UI_VERSION` | Exact media type and current proof-of-concept version. |
|
|
162
|
+
| `A2UI_MAX_DEPTH`, `A2UI_MAX_NODES` | Container-tree complexity limits. |
|
|
163
|
+
| `A2UI_MAX_SOURCE_LENGTH`, `A2UI_MAX_STRING_LENGTH` | Serialized-input and string-field limits. |
|
|
164
|
+
| `A2UI_MCP_EXTENSION_ID`, `A2UI_MCP_EXTENSION_CAPABILITIES` | Exact project-owned extension declaration. |
|
|
165
|
+
| `negotiateA2uiMcpBinding`, `A2uiMcpBindingNegotiation`, `A2uiMcpBindingGrant` | Exact-match negotiation with typed fallback reasons. |
|
|
166
|
+
| `A2UI_MCP_BINDING_VERSION`, `A2UI_MCP_PROTOCOL_VERSION`, `A2UI_MCP_SCHEMA_REVISION`, `A2UI_MCP_TRANSPORT` | Pinned binding and Candidate transport values. |
|
|
167
|
+
| `parseA2uiV1Envelope`, `parseA2uiV1Jsonl` | Schema-validate v1 lifecycle envelopes and JSONL batches. |
|
|
168
|
+
| `A2uiSurfaceStore` | Ordered lifecycle state plus policy-gated `getValidated`. |
|
|
169
|
+
| `createA2uiV1BasicCatalogPolicy`, `A2uiV1SurfaceValidationPolicy` | Explicit host allowlists for components, events, and functions. |
|
|
170
|
+
| `validateA2uiV1SurfaceState` | Revalidate a complete snapshot at another public trust boundary. |
|
|
171
|
+
| `A2UI_V1_BASIC_CATALOG_ID`, catalog name constants | Exact pinned catalog identity and selectable host capabilities. |
|
|
172
|
+
| `resolveA2uiV1JsonlFromToolResult`, `ResolvedA2uiV1JsonlResource` | Resolve a JSONL A2UI resource without using the `0.1` parser. |
|
|
173
|
+
| `A2UI_V1_PROTOCOL_VERSION`, `A2UI_V1_MAX_SOURCE_LENGTH`, `A2UI_V1_MAX_ENVELOPES`, store limit constants | v1 protocol and complexity limits. |
|
|
174
|
+
| `ResolvedA2uiResource` | URI, MIME type, and validated surface returned by the resolver. |
|
|
175
|
+
| `A2uiSurface`, `A2uiNode` | Validated surface and node unions. |
|
|
176
|
+
| Node interfaces | Typed container, text, button, and text-input nodes. |
|
|
88
177
|
|
|
89
178
|
## Security behavior
|
|
90
179
|
|
|
91
180
|
- Input is treated as untrusted at the parser boundary.
|
|
92
|
-
-
|
|
93
|
-
-
|
|
181
|
+
- Resolution requires an exact MIME type, URI match, and unambiguous text content.
|
|
182
|
+
- Errored tool results and binary A2UI resources are rejected.
|
|
183
|
+
- Unknown surface versions, node types, action types, and undeclared fields are rejected.
|
|
184
|
+
- Serialized surfaces, string fields, and tool-argument JSON graphs have fixed complexity limits.
|
|
185
|
+
- Tool arguments are recursively constrained to finite, acyclic JSON values in plain objects.
|
|
186
|
+
- JSON keys such as `__proto__` are preserved as ordinary own data properties without changing object prototypes.
|
|
94
187
|
- Parsing never resolves components or executes server-provided code.
|
|
188
|
+
- Renderer-ready v1 snapshots require a complete acyclic root-reachable graph and explicit host allowlists.
|
|
189
|
+
- Relative bindings and `@index` are accepted only inside dynamic-list template context.
|
|
190
|
+
- Catalog functions and agent events are denied unless named by host policy.
|
|
191
|
+
- `formatString` is accepted only when explicitly allowlisted and its `value` is a literal string. Its interpolation language is parsed with depth, expression-count, and cumulative-source limits; every embedded binding and named function call is validated against template scope, the host allowlist, and the pinned catalog schema. Runtime-provided format sources are rejected because their interpolations cannot be inspected before rendering. This validation does not execute renderer functions.
|
|
95
192
|
- Successful parsing does not grant device capabilities or permission to call a tool; the host still owns those decisions.
|
|
96
193
|
|
|
97
194
|
## Next layer
|
|
98
195
|
|
|
99
|
-
Use [`@mcp-native/react-native`](https://www.npmjs.com/package/@mcp-native/react-native) to
|
|
196
|
+
Use [`@mcp-native/react-native`](https://www.npmjs.com/package/@mcp-native/react-native) to adapt the supported static v1 subset or the custom `0.1` surface into a trusted native render plan. Install [`mcp-native`](https://www.npmjs.com/package/mcp-native) for the combined runtime and UI APIs.
|
|
100
197
|
|
|
101
198
|
## License
|
|
102
199
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { McpExtensionSettings } from "@mcp-native/core";
|
|
2
|
+
import { A2UI_MIME_TYPE } from "./mime.js";
|
|
3
|
+
import { A2UI_MCP_SCHEMA_REVISION } from "./schema-revision.js";
|
|
4
|
+
/** Project-owned MCP binding for carrying ordered official A2UI messages. */
|
|
5
|
+
export declare const A2UI_MCP_EXTENSION_ID: "io.github.pablospaniard/mcp-native-a2ui";
|
|
6
|
+
export declare const A2UI_MCP_BINDING_VERSION: "0.1";
|
|
7
|
+
export declare const A2UI_MCP_PROTOCOL_VERSION: "v1.0";
|
|
8
|
+
export declare const A2UI_MCP_TRANSPORT: "resource-text-jsonl";
|
|
9
|
+
/** Reuse this exact map for SDK advertisement and local negotiation. */
|
|
10
|
+
export declare const A2UI_MCP_EXTENSION_CAPABILITIES: McpExtensionSettings;
|
|
11
|
+
export type A2uiMcpBindingNegotiation = {
|
|
12
|
+
readonly kind: "fallback";
|
|
13
|
+
readonly identifier: typeof A2UI_MCP_EXTENSION_ID;
|
|
14
|
+
readonly reason: "client-unsupported" | "server-unsupported" | "incompatible-settings";
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: "negotiated";
|
|
17
|
+
readonly identifier: typeof A2UI_MCP_EXTENSION_ID;
|
|
18
|
+
readonly bindingVersion: typeof A2UI_MCP_BINDING_VERSION;
|
|
19
|
+
readonly protocolVersion: typeof A2UI_MCP_PROTOCOL_VERSION;
|
|
20
|
+
readonly schemaRevision: typeof A2UI_MCP_SCHEMA_REVISION;
|
|
21
|
+
readonly transport: typeof A2UI_MCP_TRANSPORT;
|
|
22
|
+
readonly mimeType: typeof A2UI_MIME_TYPE;
|
|
23
|
+
};
|
|
24
|
+
export type A2uiMcpBindingGrant = Extract<A2uiMcpBindingNegotiation, {
|
|
25
|
+
kind: "negotiated";
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Enables the project A2UI binding only for an exact, mutual settings match.
|
|
29
|
+
* A fallback result means callers must use ordinary MCP text/structured data.
|
|
30
|
+
*/
|
|
31
|
+
export declare function negotiateA2uiMcpBinding(clientExtensions: unknown, serverExtensions: unknown): A2uiMcpBindingNegotiation;
|
|
32
|
+
export declare function isA2uiMcpBindingGrant(value: unknown): value is A2uiMcpBindingGrant;
|
|
33
|
+
//# sourceMappingURL=binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAEzE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,6EAA6E;AAC7E,eAAO,MAAM,qBAAqB,EAAG,yCAAkD,CAAC;AACxF,eAAO,MAAM,wBAAwB,EAAG,KAAc,CAAC;AACvD,eAAO,MAAM,yBAAyB,EAAG,MAAe,CAAC;AACzD,eAAO,MAAM,kBAAkB,EAAG,qBAA8B,CAAC;AAoBjE,wEAAwE;AACxE,eAAO,MAAM,+BAA+B,EAAE,oBAE5C,CAAC;AAEH,MAAM,MAAM,yBAAyB,GACjC;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,OAAO,qBAAqB,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,GAAG,uBAAuB,CAAC;CACxF,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,OAAO,qBAAqB,CAAC;IAClD,QAAQ,CAAC,cAAc,EAAE,OAAO,wBAAwB,CAAC;IACzD,QAAQ,CAAC,eAAe,EAAE,OAAO,yBAAyB,CAAC;IAC3D,QAAQ,CAAC,cAAc,EAAE,OAAO,wBAAwB,CAAC;IACzD,QAAQ,CAAC,SAAS,EAAE,OAAO,kBAAkB,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,OAAO,cAAc,CAAC;CAC1C,CAAC;AAEN,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,yBAAyB,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAE7F;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,OAAO,EACzB,gBAAgB,EAAE,OAAO,GACxB,yBAAyB,CAgC3B;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAgBlF"}
|
package/dist/binding.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { negotiateMcpExtension } from "@mcp-native/core";
|
|
2
|
+
import { A2UI_MIME_TYPE } from "./mime.js";
|
|
3
|
+
import { A2UI_MCP_SCHEMA_REVISION } from "./schema-revision.js";
|
|
4
|
+
/** Project-owned MCP binding for carrying ordered official A2UI messages. */
|
|
5
|
+
export const A2UI_MCP_EXTENSION_ID = "io.github.pablospaniard/mcp-native-a2ui";
|
|
6
|
+
export const A2UI_MCP_BINDING_VERSION = "0.1";
|
|
7
|
+
export const A2UI_MCP_PROTOCOL_VERSION = "v1.0";
|
|
8
|
+
export const A2UI_MCP_TRANSPORT = "resource-text-jsonl";
|
|
9
|
+
const a2uiMcpSettings = Object.freeze({
|
|
10
|
+
bindingVersion: A2UI_MCP_BINDING_VERSION,
|
|
11
|
+
protocolVersion: A2UI_MCP_PROTOCOL_VERSION,
|
|
12
|
+
schemaRevision: A2UI_MCP_SCHEMA_REVISION,
|
|
13
|
+
transport: A2UI_MCP_TRANSPORT,
|
|
14
|
+
mimeType: A2UI_MIME_TYPE,
|
|
15
|
+
});
|
|
16
|
+
const A2UI_MCP_BINDING_GRANT_KEYS = [
|
|
17
|
+
"kind",
|
|
18
|
+
"identifier",
|
|
19
|
+
"bindingVersion",
|
|
20
|
+
"protocolVersion",
|
|
21
|
+
"schemaRevision",
|
|
22
|
+
"transport",
|
|
23
|
+
"mimeType",
|
|
24
|
+
];
|
|
25
|
+
/** Reuse this exact map for SDK advertisement and local negotiation. */
|
|
26
|
+
export const A2UI_MCP_EXTENSION_CAPABILITIES = Object.freeze({
|
|
27
|
+
[A2UI_MCP_EXTENSION_ID]: a2uiMcpSettings,
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Enables the project A2UI binding only for an exact, mutual settings match.
|
|
31
|
+
* A fallback result means callers must use ordinary MCP text/structured data.
|
|
32
|
+
*/
|
|
33
|
+
export function negotiateA2uiMcpBinding(clientExtensions, serverExtensions) {
|
|
34
|
+
const negotiation = negotiateMcpExtension(A2UI_MCP_EXTENSION_ID, clientExtensions, serverExtensions);
|
|
35
|
+
if (negotiation.kind === "fallback") {
|
|
36
|
+
return {
|
|
37
|
+
kind: "fallback",
|
|
38
|
+
identifier: A2UI_MCP_EXTENSION_ID,
|
|
39
|
+
reason: negotiation.reason,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!matchesA2uiMcpSettings(negotiation.clientSettings) ||
|
|
43
|
+
!matchesA2uiMcpSettings(negotiation.serverSettings)) {
|
|
44
|
+
return {
|
|
45
|
+
kind: "fallback",
|
|
46
|
+
identifier: A2UI_MCP_EXTENSION_ID,
|
|
47
|
+
reason: "incompatible-settings",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
kind: "negotiated",
|
|
52
|
+
identifier: A2UI_MCP_EXTENSION_ID,
|
|
53
|
+
bindingVersion: A2UI_MCP_BINDING_VERSION,
|
|
54
|
+
protocolVersion: A2UI_MCP_PROTOCOL_VERSION,
|
|
55
|
+
schemaRevision: A2UI_MCP_SCHEMA_REVISION,
|
|
56
|
+
transport: A2UI_MCP_TRANSPORT,
|
|
57
|
+
mimeType: A2UI_MIME_TYPE,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function isA2uiMcpBindingGrant(value) {
|
|
61
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const grant = value;
|
|
65
|
+
return (Object.keys(grant).length === A2UI_MCP_BINDING_GRANT_KEYS.length &&
|
|
66
|
+
A2UI_MCP_BINDING_GRANT_KEYS.every((key) => Object.hasOwn(grant, key)) &&
|
|
67
|
+
grant.kind === "negotiated" &&
|
|
68
|
+
grant.identifier === A2UI_MCP_EXTENSION_ID &&
|
|
69
|
+
grant.bindingVersion === A2UI_MCP_BINDING_VERSION &&
|
|
70
|
+
grant.protocolVersion === A2UI_MCP_PROTOCOL_VERSION &&
|
|
71
|
+
grant.schemaRevision === A2UI_MCP_SCHEMA_REVISION &&
|
|
72
|
+
grant.transport === A2UI_MCP_TRANSPORT &&
|
|
73
|
+
grant.mimeType === A2UI_MIME_TYPE);
|
|
74
|
+
}
|
|
75
|
+
function matchesA2uiMcpSettings(settings) {
|
|
76
|
+
return (Object.keys(settings).length === 5 &&
|
|
77
|
+
settings.bindingVersion === A2UI_MCP_BINDING_VERSION &&
|
|
78
|
+
settings.protocolVersion === A2UI_MCP_PROTOCOL_VERSION &&
|
|
79
|
+
settings.schemaRevision === A2UI_MCP_SCHEMA_REVISION &&
|
|
80
|
+
settings.transport === A2UI_MCP_TRANSPORT &&
|
|
81
|
+
settings.mimeType === A2UI_MIME_TYPE);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAGlD;CACF;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAGlD;CACF"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class A2uiParseError extends Error {
|
|
2
|
+
constructor(message, options) {
|
|
3
|
+
super(message, options);
|
|
4
|
+
this.name = "A2uiParseError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class A2uiResourceError extends Error {
|
|
8
|
+
constructor(message, options) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.name = "A2uiResourceError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe,EAAE,OAAsB;QACjD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe,EAAE,OAAsB;QACjD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import type { ToolAction } from "@mcp-native/core";
|
|
1
|
+
import type { McpToolCallResult, ToolAction } from "@mcp-native/core";
|
|
2
|
+
import { A2UI_MIME_TYPE, type A2uiResourceReader } from "./mime.js";
|
|
3
|
+
export { A2uiParseError, A2uiResourceError } from "./errors.js";
|
|
4
|
+
export { A2UI_MIME_TYPE, type A2uiResourceReader } from "./mime.js";
|
|
5
|
+
export { A2UI_MCP_SCHEMA_REVISION } from "./schema-revision.js";
|
|
6
|
+
export { A2UI_MCP_BINDING_VERSION, A2UI_MCP_EXTENSION_CAPABILITIES, A2UI_MCP_EXTENSION_ID, A2UI_MCP_PROTOCOL_VERSION, A2UI_MCP_TRANSPORT, isA2uiMcpBindingGrant, negotiateA2uiMcpBinding, } from "./binding.js";
|
|
7
|
+
export type { A2uiMcpBindingGrant, A2uiMcpBindingNegotiation } from "./binding.js";
|
|
8
|
+
export { A2UI_V1_MAX_ENVELOPES, A2UI_V1_MAX_SOURCE_LENGTH, A2UI_V1_PROTOCOL_VERSION, A2UI_V1_BASIC_CATALOG_ID, A2UI_V1_BASIC_COMPONENT_NAMES, A2UI_V1_BASIC_FUNCTION_NAMES, A2UI_V1_SYSTEM_FUNCTION_NAMES, A2UI_V1_MAX_COMPONENTS, A2UI_V1_MAX_SURFACES, A2uiSurfaceStore, createA2uiV1BasicCatalogPolicy, parseA2uiV1Envelope, parseA2uiV1Jsonl, resolveA2uiV1JsonlFromToolResult, validateA2uiV1SurfaceState, } from "./v1/index.js";
|
|
9
|
+
export type { A2uiV1BasicCatalogPolicyOptions, A2uiV1Component, A2uiV1CreateSurfaceEnvelope, A2uiV1DeleteSurfaceEnvelope, A2uiV1Envelope, A2uiV1EnvelopeKind, A2uiV1SurfaceState, A2uiV1SurfaceValidationPolicy, A2uiV1UpdateComponentsEnvelope, A2uiV1UpdateDataModelEnvelope, ResolvedA2uiV1JsonlResource, } from "./v1/index.js";
|
|
2
10
|
export declare const A2UI_VERSION: "0.1";
|
|
11
|
+
/** Maximum nesting depth for container trees (root is depth 0). */
|
|
12
|
+
export declare const A2UI_MAX_DEPTH = 32;
|
|
13
|
+
/** Maximum number of nodes allowed in a single surface. */
|
|
14
|
+
export declare const A2UI_MAX_NODES = 256;
|
|
15
|
+
/** Maximum UTF-16 code units accepted in a serialized surface. */
|
|
16
|
+
export declare const A2UI_MAX_SOURCE_LENGTH = 1048576;
|
|
17
|
+
/** Maximum UTF-16 code units accepted in one surface string field. */
|
|
18
|
+
export declare const A2UI_MAX_STRING_LENGTH = 65536;
|
|
3
19
|
interface A2uiNodeBase {
|
|
4
20
|
readonly id: string;
|
|
5
21
|
}
|
|
@@ -27,9 +43,15 @@ export interface A2uiSurface {
|
|
|
27
43
|
readonly version: typeof A2UI_VERSION;
|
|
28
44
|
readonly root: A2uiNode;
|
|
29
45
|
}
|
|
30
|
-
export
|
|
31
|
-
|
|
46
|
+
export interface ResolvedA2uiResource {
|
|
47
|
+
readonly uri: string;
|
|
48
|
+
readonly mimeType: typeof A2UI_MIME_TYPE;
|
|
49
|
+
readonly surface: A2uiSurface;
|
|
32
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the single explicitly typed A2UI resource link in a successful
|
|
53
|
+
* tool result, reads it through the host client, and validates its surface.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveA2uiResourceFromToolResult(reader: A2uiResourceReader, toolResult: McpToolCallResult): Promise<ResolvedA2uiResource>;
|
|
33
56
|
export declare function parseA2uiSurface(input: string | unknown): A2uiSurface;
|
|
34
|
-
export {};
|
|
35
57
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,57 @@
|
|
|
1
|
+
import { parseMcpNativeAction } from "@mcp-native/core";
|
|
2
|
+
import { A2uiParseError, A2uiResourceError } from "./errors.js";
|
|
3
|
+
import { A2UI_MIME_TYPE } from "./mime.js";
|
|
4
|
+
export { A2uiParseError, A2uiResourceError } from "./errors.js";
|
|
5
|
+
export { A2UI_MIME_TYPE } from "./mime.js";
|
|
6
|
+
export { A2UI_MCP_SCHEMA_REVISION } from "./schema-revision.js";
|
|
7
|
+
export { A2UI_MCP_BINDING_VERSION, A2UI_MCP_EXTENSION_CAPABILITIES, A2UI_MCP_EXTENSION_ID, A2UI_MCP_PROTOCOL_VERSION, A2UI_MCP_TRANSPORT, isA2uiMcpBindingGrant, negotiateA2uiMcpBinding, } from "./binding.js";
|
|
8
|
+
export { A2UI_V1_MAX_ENVELOPES, A2UI_V1_MAX_SOURCE_LENGTH, A2UI_V1_PROTOCOL_VERSION, A2UI_V1_BASIC_CATALOG_ID, A2UI_V1_BASIC_COMPONENT_NAMES, A2UI_V1_BASIC_FUNCTION_NAMES, A2UI_V1_SYSTEM_FUNCTION_NAMES, A2UI_V1_MAX_COMPONENTS, A2UI_V1_MAX_SURFACES, A2uiSurfaceStore, createA2uiV1BasicCatalogPolicy, parseA2uiV1Envelope, parseA2uiV1Jsonl, resolveA2uiV1JsonlFromToolResult, validateA2uiV1SurfaceState, } from "./v1/index.js";
|
|
1
9
|
export const A2UI_VERSION = "0.1";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
10
|
+
/** Maximum nesting depth for container trees (root is depth 0). */
|
|
11
|
+
export const A2UI_MAX_DEPTH = 32;
|
|
12
|
+
/** Maximum number of nodes allowed in a single surface. */
|
|
13
|
+
export const A2UI_MAX_NODES = 256;
|
|
14
|
+
/** Maximum UTF-16 code units accepted in a serialized surface. */
|
|
15
|
+
export const A2UI_MAX_SOURCE_LENGTH = 1_048_576;
|
|
16
|
+
/** Maximum UTF-16 code units accepted in one surface string field. */
|
|
17
|
+
export const A2UI_MAX_STRING_LENGTH = 65_536;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves the single explicitly typed A2UI resource link in a successful
|
|
20
|
+
* tool result, reads it through the host client, and validates its surface.
|
|
21
|
+
*/
|
|
22
|
+
export async function resolveA2uiResourceFromToolResult(reader, toolResult) {
|
|
23
|
+
if (toolResult.isError !== undefined && typeof toolResult.isError !== "boolean") {
|
|
24
|
+
throw new A2uiResourceError("Expected tool result.isError to be a boolean");
|
|
6
25
|
}
|
|
26
|
+
if (toolResult.isError === true) {
|
|
27
|
+
throw new A2uiResourceError("Cannot resolve an A2UI resource from an errored tool result");
|
|
28
|
+
}
|
|
29
|
+
const links = expectContentArray(toolResult.content).flatMap((block, index) => parseA2uiResourceLink(block, `tool result.content[${index}]`));
|
|
30
|
+
if (links.length !== 1) {
|
|
31
|
+
throw new A2uiResourceError(`Expected exactly one ${A2UI_MIME_TYPE} resource link, received ${links.length}`);
|
|
32
|
+
}
|
|
33
|
+
const link = links[0];
|
|
34
|
+
const readResult = await reader.readResource(link.uri);
|
|
35
|
+
const resources = expectResourceContents(readResult).filter((resource) => resource.uri === link.uri && resource.mimeType === A2UI_MIME_TYPE);
|
|
36
|
+
if (resources.length !== 1) {
|
|
37
|
+
throw new A2uiResourceError(`Expected exactly one ${A2UI_MIME_TYPE} text resource for ${link.uri}, received ${resources.length}`);
|
|
38
|
+
}
|
|
39
|
+
const resource = resources[0];
|
|
40
|
+
if (typeof resource.text !== "string" || resource.blob !== undefined) {
|
|
41
|
+
throw new A2uiResourceError(`Expected a text-only A2UI resource for ${link.uri}`);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
uri: link.uri,
|
|
45
|
+
mimeType: A2UI_MIME_TYPE,
|
|
46
|
+
surface: parseA2uiSurface(resource.text),
|
|
47
|
+
};
|
|
7
48
|
}
|
|
8
49
|
export function parseA2uiSurface(input) {
|
|
9
50
|
let value = input;
|
|
10
51
|
if (typeof input === "string") {
|
|
52
|
+
if (input.length > A2UI_MAX_SOURCE_LENGTH) {
|
|
53
|
+
throw new A2uiParseError(`A2UI source exceeds maximum length of ${A2UI_MAX_SOURCE_LENGTH}`);
|
|
54
|
+
}
|
|
11
55
|
try {
|
|
12
56
|
value = JSON.parse(input);
|
|
13
57
|
}
|
|
@@ -17,29 +61,44 @@ export function parseA2uiSurface(input) {
|
|
|
17
61
|
}
|
|
18
62
|
}
|
|
19
63
|
const surface = expectObject(value, "surface");
|
|
64
|
+
expectOnlyKeys(surface, ["root", "version"], "surface");
|
|
20
65
|
const version = expectString(surface.version, "surface.version");
|
|
21
66
|
if (version !== A2UI_VERSION) {
|
|
22
67
|
throw new A2uiParseError(`Unsupported A2UI version: ${version}`);
|
|
23
68
|
}
|
|
69
|
+
const seenIds = new Set();
|
|
24
70
|
return {
|
|
25
71
|
version: A2UI_VERSION,
|
|
26
|
-
root: parseNode(surface.root, "surface.root"),
|
|
72
|
+
root: parseNode(surface.root, "surface.root", seenIds, 0),
|
|
27
73
|
};
|
|
28
74
|
}
|
|
29
|
-
function parseNode(value, path) {
|
|
75
|
+
function parseNode(value, path, seenIds, depth) {
|
|
76
|
+
if (depth > A2UI_MAX_DEPTH) {
|
|
77
|
+
throw new A2uiParseError(`A2UI surface exceeds maximum depth of ${A2UI_MAX_DEPTH} at ${path}`);
|
|
78
|
+
}
|
|
79
|
+
if (seenIds.size >= A2UI_MAX_NODES) {
|
|
80
|
+
throw new A2uiParseError(`A2UI surface exceeds maximum of ${A2UI_MAX_NODES} nodes`);
|
|
81
|
+
}
|
|
30
82
|
const node = expectObject(value, path);
|
|
31
83
|
const id = expectString(node.id, `${path}.id`);
|
|
84
|
+
if (seenIds.has(id)) {
|
|
85
|
+
throw new A2uiParseError(`Duplicate node id at ${path}: ${id}`);
|
|
86
|
+
}
|
|
87
|
+
seenIds.add(id);
|
|
32
88
|
const type = expectString(node.type, `${path}.type`);
|
|
33
89
|
switch (type) {
|
|
34
90
|
case "container":
|
|
91
|
+
expectOnlyKeys(node, ["children", "id", "type"], path);
|
|
35
92
|
return {
|
|
36
93
|
id,
|
|
37
94
|
type,
|
|
38
|
-
children: expectArray(node.children, `${path}.children`).map((child, index) => parseNode(child, `${path}.children[${index}]
|
|
95
|
+
children: expectArray(node.children, `${path}.children`).map((child, index) => parseNode(child, `${path}.children[${index}]`, seenIds, depth + 1)),
|
|
39
96
|
};
|
|
40
97
|
case "text":
|
|
98
|
+
expectOnlyKeys(node, ["id", "text", "type"], path);
|
|
41
99
|
return { id, type, text: expectString(node.text, `${path}.text`) };
|
|
42
100
|
case "button":
|
|
101
|
+
expectOnlyKeys(node, ["action", "id", "label", "type"], path);
|
|
43
102
|
return {
|
|
44
103
|
id,
|
|
45
104
|
type,
|
|
@@ -47,6 +106,7 @@ function parseNode(value, path) {
|
|
|
47
106
|
action: parseToolAction(node.action, `${path}.action`),
|
|
48
107
|
};
|
|
49
108
|
case "text-input": {
|
|
109
|
+
expectOnlyKeys(node, ["binding", "id", "label", "type", "value"], path);
|
|
50
110
|
const label = expectString(node.label, `${path}.label`);
|
|
51
111
|
const valueField = optionalString(node.value, `${path}.value`);
|
|
52
112
|
const binding = optionalString(node.binding, `${path}.binding`);
|
|
@@ -63,26 +123,95 @@ function parseNode(value, path) {
|
|
|
63
123
|
}
|
|
64
124
|
}
|
|
65
125
|
function parseToolAction(value, path) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
126
|
+
try {
|
|
127
|
+
return parseMcpNativeAction(value, path);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const message = error instanceof Error ? error.message : `Invalid tool action at ${path}`;
|
|
131
|
+
throw new A2uiParseError(message, { cause: error });
|
|
70
132
|
}
|
|
71
|
-
const arguments_ = action.arguments;
|
|
72
|
-
return {
|
|
73
|
-
type,
|
|
74
|
-
name: expectString(action.name, `${path}.name`),
|
|
75
|
-
...(arguments_ === undefined
|
|
76
|
-
? {}
|
|
77
|
-
: { arguments: expectJsonObject(arguments_, `${path}.arguments`) }),
|
|
78
|
-
};
|
|
79
133
|
}
|
|
80
134
|
function expectObject(value, path) {
|
|
81
135
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
82
136
|
throw new A2uiParseError(`Expected an object at ${path}`);
|
|
83
137
|
}
|
|
138
|
+
const prototype = Object.getPrototypeOf(value);
|
|
139
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
140
|
+
throw new A2uiParseError(`Expected a plain object at ${path}`);
|
|
141
|
+
}
|
|
142
|
+
const ownProperties = Object.create(null);
|
|
143
|
+
for (const key of Object.keys(value)) {
|
|
144
|
+
ownProperties[key] = value[key];
|
|
145
|
+
}
|
|
146
|
+
return ownProperties;
|
|
147
|
+
}
|
|
148
|
+
function expectContentArray(value) {
|
|
149
|
+
if (!Array.isArray(value)) {
|
|
150
|
+
throw new A2uiResourceError("Expected an array at tool result.content");
|
|
151
|
+
}
|
|
84
152
|
return value;
|
|
85
153
|
}
|
|
154
|
+
function parseA2uiResourceLink(value, path) {
|
|
155
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
156
|
+
throw new A2uiResourceError(`Expected a content object at ${path}`);
|
|
157
|
+
}
|
|
158
|
+
const block = value;
|
|
159
|
+
if (typeof block.type !== "string") {
|
|
160
|
+
throw new A2uiResourceError(`Expected a content type at ${path}.type`);
|
|
161
|
+
}
|
|
162
|
+
if (block.type !== "resource_link") {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
if (typeof block.name !== "string") {
|
|
166
|
+
throw new A2uiResourceError(`Expected a string at ${path}.name`);
|
|
167
|
+
}
|
|
168
|
+
if (typeof block.uri !== "string") {
|
|
169
|
+
throw new A2uiResourceError(`Expected a string at ${path}.uri`);
|
|
170
|
+
}
|
|
171
|
+
if (block.mimeType !== undefined && typeof block.mimeType !== "string") {
|
|
172
|
+
throw new A2uiResourceError(`Expected a string at ${path}.mimeType`);
|
|
173
|
+
}
|
|
174
|
+
return block.mimeType === A2UI_MIME_TYPE ? [{ uri: block.uri }] : [];
|
|
175
|
+
}
|
|
176
|
+
function expectResourceContents(value) {
|
|
177
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
178
|
+
throw new A2uiResourceError("Expected an object from resources/read");
|
|
179
|
+
}
|
|
180
|
+
const contents = value.contents;
|
|
181
|
+
if (!Array.isArray(contents)) {
|
|
182
|
+
throw new A2uiResourceError("Expected an array at resource result.contents");
|
|
183
|
+
}
|
|
184
|
+
return contents.map((content, index) => {
|
|
185
|
+
if (content === null || typeof content !== "object" || Array.isArray(content)) {
|
|
186
|
+
throw new A2uiResourceError(`Expected an object at resource result.contents[${index}]`);
|
|
187
|
+
}
|
|
188
|
+
const resource = content;
|
|
189
|
+
const uri = resource.uri;
|
|
190
|
+
if (typeof uri !== "string") {
|
|
191
|
+
throw new A2uiResourceError(`Expected a string at resource result.contents[${index}].uri`);
|
|
192
|
+
}
|
|
193
|
+
const mimeType = resource.mimeType;
|
|
194
|
+
if (mimeType !== undefined && typeof mimeType !== "string") {
|
|
195
|
+
throw new A2uiResourceError(`Expected a string at resource result.contents[${index}].mimeType`);
|
|
196
|
+
}
|
|
197
|
+
const text = resource.text;
|
|
198
|
+
if (text !== undefined && typeof text !== "string") {
|
|
199
|
+
throw new A2uiResourceError(`Expected a string at resource result.contents[${index}].text`);
|
|
200
|
+
}
|
|
201
|
+
const blob = resource.blob;
|
|
202
|
+
if (blob !== undefined && typeof blob !== "string") {
|
|
203
|
+
throw new A2uiResourceError(`Expected a string at resource result.contents[${index}].blob`);
|
|
204
|
+
}
|
|
205
|
+
const common = {
|
|
206
|
+
uri,
|
|
207
|
+
...(mimeType === undefined ? {} : { mimeType }),
|
|
208
|
+
};
|
|
209
|
+
if ((text === undefined) === (blob === undefined)) {
|
|
210
|
+
throw new A2uiResourceError(`Expected exactly one of text or blob at resource result.contents[${index}]`);
|
|
211
|
+
}
|
|
212
|
+
return text === undefined ? { ...common, blob: blob } : { ...common, text };
|
|
213
|
+
});
|
|
214
|
+
}
|
|
86
215
|
function expectArray(value, path) {
|
|
87
216
|
if (!Array.isArray(value)) {
|
|
88
217
|
throw new A2uiParseError(`Expected an array at ${path}`);
|
|
@@ -93,29 +222,20 @@ function expectString(value, path) {
|
|
|
93
222
|
if (typeof value !== "string") {
|
|
94
223
|
throw new A2uiParseError(`Expected a string at ${path}`);
|
|
95
224
|
}
|
|
225
|
+
if (value.length > A2UI_MAX_STRING_LENGTH) {
|
|
226
|
+
throw new A2uiParseError(`String at ${path} exceeds maximum length of ${A2UI_MAX_STRING_LENGTH}`);
|
|
227
|
+
}
|
|
96
228
|
return value;
|
|
97
229
|
}
|
|
98
230
|
function optionalString(value, path) {
|
|
99
231
|
return value === undefined ? undefined : expectString(value, path);
|
|
100
232
|
}
|
|
101
|
-
function
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return result;
|
|
108
|
-
}
|
|
109
|
-
function expectJsonValue(value, path) {
|
|
110
|
-
if (value === null ||
|
|
111
|
-
typeof value === "boolean" ||
|
|
112
|
-
typeof value === "number" ||
|
|
113
|
-
typeof value === "string") {
|
|
114
|
-
return value;
|
|
115
|
-
}
|
|
116
|
-
if (Array.isArray(value)) {
|
|
117
|
-
return value.map((child, index) => expectJsonValue(child, `${path}[${index}]`));
|
|
233
|
+
function expectOnlyKeys(object, allowedKeys, path) {
|
|
234
|
+
const allowed = new Set(allowedKeys);
|
|
235
|
+
for (const key of Object.keys(object)) {
|
|
236
|
+
if (!allowed.has(key)) {
|
|
237
|
+
throw new A2uiParseError(`Unsupported field ${JSON.stringify(key)} at ${path}`);
|
|
238
|
+
}
|
|
118
239
|
}
|
|
119
|
-
return expectJsonObject(value, path);
|
|
120
240
|
}
|
|
121
241
|
//# sourceMappingURL=index.js.map
|