@webmcp-auto-ui/core 0.5.0 → 2.5.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/README.md +39 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -1
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/multi-client.d.ts +7 -3
- package/dist/multi-client.d.ts.map +1 -1
- package/dist/multi-client.js +49 -4
- package/dist/multi-client.js.map +1 -1
- package/dist/utils.d.ts +27 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +136 -3
- package/dist/utils.js.map +1 -1
- package/dist/webmcp-helpers.d.ts +0 -16
- package/dist/webmcp-helpers.d.ts.map +1 -1
- package/dist/webmcp-helpers.js +5 -47
- package/dist/webmcp-helpers.js.map +1 -1
- package/dist/webmcp-server.d.ts +54 -0
- package/dist/webmcp-server.d.ts.map +1 -0
- package/dist/webmcp-server.js +425 -0
- package/dist/webmcp-server.js.map +1 -0
- package/package.json +1 -1
- package/src/client.d.ts +26 -0
- package/src/client.ts +3 -1
- package/src/events.d.ts +16 -0
- package/src/index.d.ts +13 -0
- package/src/index.ts +9 -7
- package/src/multi-client.d.ts +55 -0
- package/src/multi-client.ts +53 -4
- package/src/polyfill.d.ts +51 -0
- package/src/types.d.ts +331 -0
- package/src/utils.d.ts +81 -0
- package/src/utils.ts +163 -0
- package/src/validate.d.ts +13 -0
- package/src/webmcp-helpers.d.ts +3 -0
- package/src/webmcp-helpers.ts +6 -63
- package/src/webmcp-server.d.ts +53 -0
- package/src/webmcp-server.ts +538 -0
package/src/utils.ts
CHANGED
|
@@ -75,6 +75,86 @@ export function signalCompletion(completionEventName: string, detail?: unknown):
|
|
|
75
75
|
// Item 19 — sanitizeSchema (for AI SDKs)
|
|
76
76
|
// ---------------------------------------------------------------------------
|
|
77
77
|
|
|
78
|
+
export interface SchemaPatch {
|
|
79
|
+
path: string; // e.g. "root", "properties.params", "properties.data.items"
|
|
80
|
+
type: 'additionalProperties'; // what was patched
|
|
81
|
+
value: false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sanitize schema AND report patches applied for strict tool use.
|
|
86
|
+
* Use this when you need visibility into what was changed.
|
|
87
|
+
*/
|
|
88
|
+
export function sanitizeSchemaWithReport(schema: JsonSchema): { schema: JsonSchema; patches: SchemaPatch[] } {
|
|
89
|
+
if (typeof schema === 'boolean') return { schema, patches: [] };
|
|
90
|
+
const patches: SchemaPatch[] = [];
|
|
91
|
+
const result = sanitizeSchemaObjectWithReport({ ...schema } as JsonSchemaObject, new WeakSet(), 'root', patches);
|
|
92
|
+
return { schema: result, patches };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sanitizeSchemaObjectWithReport(obj: JsonSchemaObject, seen: WeakSet<object>, path: string, patches: SchemaPatch[]): JsonSchemaObject {
|
|
96
|
+
if (seen.has(obj)) return obj;
|
|
97
|
+
seen.add(obj);
|
|
98
|
+
|
|
99
|
+
const result = { ...obj };
|
|
100
|
+
|
|
101
|
+
// Remove composition keywords that AI SDKs can't handle
|
|
102
|
+
delete result.oneOf;
|
|
103
|
+
delete result.anyOf;
|
|
104
|
+
delete result.allOf;
|
|
105
|
+
delete result.not;
|
|
106
|
+
|
|
107
|
+
// Remove conditional keywords
|
|
108
|
+
delete result.if;
|
|
109
|
+
delete result.then;
|
|
110
|
+
delete result.else;
|
|
111
|
+
|
|
112
|
+
// Remove $ref (needs dereferencing first)
|
|
113
|
+
delete result.$ref;
|
|
114
|
+
|
|
115
|
+
// Recursively sanitize nested schemas
|
|
116
|
+
if (result.properties) {
|
|
117
|
+
const sanitizedProps: Record<string, JsonSchema> = {};
|
|
118
|
+
for (const [key, value] of Object.entries(result.properties)) {
|
|
119
|
+
sanitizedProps[key] = typeof value === 'boolean'
|
|
120
|
+
? value
|
|
121
|
+
: sanitizeSchemaObjectWithReport({ ...value } as JsonSchemaObject, seen, `${path}.properties.${key}`, patches);
|
|
122
|
+
}
|
|
123
|
+
result.properties = sanitizedProps;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (result.items) {
|
|
127
|
+
if (Array.isArray(result.items)) {
|
|
128
|
+
result.items = result.items.map((item, i) =>
|
|
129
|
+
typeof item === 'boolean'
|
|
130
|
+
? item
|
|
131
|
+
: sanitizeSchemaObjectWithReport({ ...item } as JsonSchemaObject, seen, `${path}.items[${i}]`, patches)
|
|
132
|
+
);
|
|
133
|
+
} else if (typeof result.items !== 'boolean') {
|
|
134
|
+
result.items = sanitizeSchemaObjectWithReport({ ...result.items } as JsonSchemaObject, seen, `${path}.items`, patches);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Strict tool use: ensure additionalProperties is set on any object type
|
|
139
|
+
if ((result.type === 'object' || result.properties) && !('additionalProperties' in result)) {
|
|
140
|
+
result.additionalProperties = false;
|
|
141
|
+
patches.push({ path, type: 'additionalProperties', value: false });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (result.additionalProperties && typeof result.additionalProperties === 'object') {
|
|
145
|
+
result.additionalProperties = sanitizeSchemaObjectWithReport(
|
|
146
|
+
{ ...result.additionalProperties } as JsonSchemaObject,
|
|
147
|
+
seen,
|
|
148
|
+
`${path}.additionalProperties`,
|
|
149
|
+
patches
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
78
158
|
/**
|
|
79
159
|
* Strip JSON Schema keywords that cause errors in AI SDKs
|
|
80
160
|
* (e.g., Vercel AI SDK rejects oneOf/anyOf on non-STRING types).
|
|
@@ -129,6 +209,12 @@ function sanitizeSchemaObject(obj: JsonSchemaObject, seen: WeakSet<object>): Jso
|
|
|
129
209
|
}
|
|
130
210
|
}
|
|
131
211
|
|
|
212
|
+
// Strict tool use: ensure additionalProperties is set on any object type
|
|
213
|
+
// Anthropic requires this on ALL objects, even without properties
|
|
214
|
+
if ((result.type === 'object' || result.properties) && !('additionalProperties' in result)) {
|
|
215
|
+
result.additionalProperties = false;
|
|
216
|
+
}
|
|
217
|
+
|
|
132
218
|
if (result.additionalProperties && typeof result.additionalProperties === 'object') {
|
|
133
219
|
result.additionalProperties = sanitizeSchemaObject(
|
|
134
220
|
{ ...result.additionalProperties } as JsonSchemaObject,
|
|
@@ -139,6 +225,83 @@ function sanitizeSchemaObject(obj: JsonSchemaObject, seen: WeakSet<object>): Jso
|
|
|
139
225
|
return result;
|
|
140
226
|
}
|
|
141
227
|
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// flattenSchema — flatten nested object properties for small LLMs
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Flatten nested object schemas into flat key__subkey properties.
|
|
234
|
+
* Returns { schema, pathMap } where pathMap maps flat keys to nested paths.
|
|
235
|
+
* Only flattens properties of type "object" with their own properties.
|
|
236
|
+
*/
|
|
237
|
+
export function flattenSchema(schema: JsonSchema): { schema: JsonSchema; pathMap: Record<string, string[]> } {
|
|
238
|
+
if (typeof schema === 'boolean') return { schema, pathMap: {} };
|
|
239
|
+
const obj = schema as JsonSchemaObject;
|
|
240
|
+
if (!obj.properties) return { schema, pathMap: {} };
|
|
241
|
+
|
|
242
|
+
const flatProps: Record<string, JsonSchema> = {};
|
|
243
|
+
const pathMap: Record<string, string[]> = {};
|
|
244
|
+
const required = new Set(obj.required ?? []);
|
|
245
|
+
const flatRequired: string[] = [];
|
|
246
|
+
|
|
247
|
+
for (const [key, prop] of Object.entries(obj.properties)) {
|
|
248
|
+
if (typeof prop !== 'boolean' && prop.type === 'object' && prop.properties) {
|
|
249
|
+
// Flatten nested object
|
|
250
|
+
const nestedRequired = new Set((prop as JsonSchemaObject).required ?? []);
|
|
251
|
+
for (const [subKey, subProp] of Object.entries(prop.properties!)) {
|
|
252
|
+
const flatKey = `${key}__${subKey}`;
|
|
253
|
+
flatProps[flatKey] = subProp;
|
|
254
|
+
pathMap[flatKey] = [key, subKey];
|
|
255
|
+
if (required.has(key) && nestedRequired.has(subKey)) {
|
|
256
|
+
flatRequired.push(flatKey);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
// Keep as-is
|
|
261
|
+
flatProps[key] = prop;
|
|
262
|
+
pathMap[key] = [key];
|
|
263
|
+
if (required.has(key)) flatRequired.push(key);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const result: JsonSchemaObject = {
|
|
268
|
+
...obj,
|
|
269
|
+
properties: flatProps,
|
|
270
|
+
};
|
|
271
|
+
if (flatRequired.length > 0) result.required = flatRequired;
|
|
272
|
+
else delete result.required;
|
|
273
|
+
|
|
274
|
+
return { schema: result, pathMap };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Unflatten params using a pathMap from flattenSchema.
|
|
279
|
+
* Converts { a__b: 1, a__c: 2, d: 3 } back to { a: { b: 1, c: 2 }, d: 3 }
|
|
280
|
+
*/
|
|
281
|
+
export function unflattenParams(
|
|
282
|
+
params: Record<string, unknown>,
|
|
283
|
+
pathMap: Record<string, string[]>
|
|
284
|
+
): Record<string, unknown> {
|
|
285
|
+
const result: Record<string, unknown> = {};
|
|
286
|
+
for (const [flatKey, value] of Object.entries(params)) {
|
|
287
|
+
const path = pathMap[flatKey] ?? flatKey.split('__');
|
|
288
|
+
if (path.length === 1) {
|
|
289
|
+
result[path[0]] = value;
|
|
290
|
+
} else {
|
|
291
|
+
// Build nested structure
|
|
292
|
+
let current = result;
|
|
293
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
294
|
+
if (!(path[i] in current) || typeof current[path[i]] !== 'object') {
|
|
295
|
+
current[path[i]] = {};
|
|
296
|
+
}
|
|
297
|
+
current = current[path[i]] as Record<string, unknown>;
|
|
298
|
+
}
|
|
299
|
+
current[path[path.length - 1]] = value;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
|
|
142
305
|
// ---------------------------------------------------------------------------
|
|
143
306
|
// createToolGroup — ergonomic helper for SPA route-based tool registration
|
|
144
307
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { JsonSchema } from './types.js';
|
|
2
|
+
export interface ValidationError {
|
|
3
|
+
path: string;
|
|
4
|
+
message: string;
|
|
5
|
+
keyword: string;
|
|
6
|
+
expected?: unknown;
|
|
7
|
+
actual?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface ValidationResult {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
errors: ValidationError[];
|
|
12
|
+
}
|
|
13
|
+
export declare function validateJsonSchema(value: unknown, schema: JsonSchema, path?: string): ValidationResult;
|
package/src/webmcp-helpers.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// @webmcp-auto-ui/core — WebMCP helpers
|
|
3
|
-
//
|
|
3
|
+
// Result builders for tool execute callbacks.
|
|
4
4
|
// Zero additional dependencies. SSR-safe.
|
|
5
|
+
//
|
|
6
|
+
// NOTE: The skill registry that used to live here (SkillDef, registerSkill,
|
|
7
|
+
// unregisterSkill, etc.) has been removed. The canonical skill type and
|
|
8
|
+
// registry now live in @webmcp-auto-ui/sdk (packages/sdk/src/skills/registry.ts).
|
|
5
9
|
// ---------------------------------------------------------------------------
|
|
6
10
|
|
|
7
|
-
import {
|
|
8
|
-
import type { ModelContextTool, ToolExecuteCallback, ToolExecuteResult } from './types.js';
|
|
11
|
+
import type { ToolExecuteResult } from './types.js';
|
|
9
12
|
|
|
10
13
|
// ---------------------------------------------------------------------------
|
|
11
14
|
// Result builders
|
|
@@ -18,63 +21,3 @@ export function textResult(text: string): ToolExecuteResult {
|
|
|
18
21
|
export function jsonResult(data: unknown): ToolExecuteResult {
|
|
19
22
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
20
23
|
}
|
|
21
|
-
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Skill registry
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
export interface SkillDef {
|
|
27
|
-
id: string;
|
|
28
|
-
name: string;
|
|
29
|
-
description: string;
|
|
30
|
-
component: string;
|
|
31
|
-
presentation?: string;
|
|
32
|
-
version?: string;
|
|
33
|
-
tags?: string[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const _skills = new Map<string, SkillDef>();
|
|
37
|
-
|
|
38
|
-
function mcNav(): {
|
|
39
|
-
registerTool: (t: ModelContextTool & { execute: ToolExecuteCallback }) => void;
|
|
40
|
-
unregisterTool: (name: string) => void;
|
|
41
|
-
} | null {
|
|
42
|
-
if (typeof navigator === 'undefined') return null;
|
|
43
|
-
return (navigator as unknown as Record<string, unknown>).modelContext as ReturnType<typeof mcNav> ?? null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function registerSkill(skill: SkillDef): void {
|
|
47
|
-
_skills.set(skill.id, skill);
|
|
48
|
-
const mc = mcNav();
|
|
49
|
-
if (!mc) return;
|
|
50
|
-
const toolName = `skill__${skill.id}`;
|
|
51
|
-
try { mc.unregisterTool(toolName); } catch { /* not yet registered */ }
|
|
52
|
-
mc.registerTool({
|
|
53
|
-
name: toolName,
|
|
54
|
-
description: `[Skill] ${skill.name} — ${skill.description}. Component: ${skill.component}.${skill.presentation ? ` Hints: ${skill.presentation}` : ''}`,
|
|
55
|
-
inputSchema: { type: 'object', properties: {} },
|
|
56
|
-
execute: () => jsonResult(skill),
|
|
57
|
-
annotations: { readOnlyHint: true },
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function unregisterSkill(id: string): void {
|
|
62
|
-
_skills.delete(id);
|
|
63
|
-
const mc = mcNav();
|
|
64
|
-
if (!mc) return;
|
|
65
|
-
try { mc.unregisterTool(`skill__${id}`); } catch { /* already gone */ }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function getSkill(id: string): SkillDef | undefined {
|
|
69
|
-
return _skills.get(id);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function listSkills(): SkillDef[] {
|
|
73
|
-
return Array.from(_skills.values());
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function clearSkills(): void {
|
|
77
|
-
for (const id of Array.from(_skills.keys())) unregisterSkill(id);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export { executeToolInternal };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface WebMcpServerOptions {
|
|
2
|
+
description: string;
|
|
3
|
+
}
|
|
4
|
+
export interface WebMcpToolDef {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
inputSchema: Record<string, unknown>;
|
|
8
|
+
execute: (params: Record<string, unknown>) => Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
/** A vanilla renderer: receives a container + data, optionally returns a cleanup function. */
|
|
11
|
+
export type WidgetRenderer = ((container: HTMLElement, data: Record<string, unknown>) => void | (() => void)) | unknown;
|
|
12
|
+
export interface WidgetEntry {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
inputSchema: Record<string, unknown>;
|
|
16
|
+
recipe: string;
|
|
17
|
+
renderer: WidgetRenderer;
|
|
18
|
+
group?: string;
|
|
19
|
+
/** True when the renderer is a plain function (not a framework component). */
|
|
20
|
+
vanilla: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface WebMcpServer {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly description: string;
|
|
25
|
+
registerWidget(recipeMarkdown: string, renderer: WidgetRenderer): void;
|
|
26
|
+
addTool(tool: WebMcpToolDef): void;
|
|
27
|
+
layer(): {
|
|
28
|
+
protocol: 'webmcp';
|
|
29
|
+
serverName: string;
|
|
30
|
+
description: string;
|
|
31
|
+
tools: WebMcpToolDef[];
|
|
32
|
+
};
|
|
33
|
+
getWidget(name: string): WidgetEntry | undefined;
|
|
34
|
+
listWidgets(): WidgetEntry[];
|
|
35
|
+
}
|
|
36
|
+
export interface ParsedFrontmatter {
|
|
37
|
+
frontmatter: Record<string, unknown>;
|
|
38
|
+
body: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Parse a markdown file with YAML frontmatter (--- delimited).
|
|
42
|
+
* Supports: scalars, nested objects (indentation), arrays (- item), inline values.
|
|
43
|
+
* No external YAML dependency.
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseFrontmatter(markdown: string): ParsedFrontmatter;
|
|
46
|
+
/**
|
|
47
|
+
* Mount a widget into a DOM container by searching registered servers.
|
|
48
|
+
* If the renderer is a function (vanilla renderer), it is called directly.
|
|
49
|
+
* Returns an optional cleanup function.
|
|
50
|
+
* Falls back to a text placeholder if no server provides the widget.
|
|
51
|
+
*/
|
|
52
|
+
export declare function mountWidget(container: HTMLElement, type: string, data: Record<string, unknown>, servers: WebMcpServer[]): (() => void) | void;
|
|
53
|
+
export declare function createWebMcpServer(name: string, options: WebMcpServerOptions): WebMcpServer;
|