@json-layout/core 2.4.0 → 2.5.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/package.json +8 -1
- package/src/i18n/de.js +0 -19
- package/src/i18n/en.js +0 -19
- package/src/i18n/fr.js +0 -19
- package/src/i18n/nl.js +0 -19
- package/src/i18n/types.ts +0 -19
- package/src/state/state-tree.js +10 -1
- package/src/webmcp/index.js +283 -0
- package/src/webmcp/project.js +148 -0
- package/src/webmcp/resolve.js +25 -0
- package/src/webmcp/tools/describe-state.js +74 -0
- package/src/webmcp/tools/fill-form-skill.js +67 -0
- package/src/webmcp/tools/get-data.js +36 -0
- package/src/webmcp/tools/get-field-suggestions.js +84 -0
- package/src/webmcp/tools/set-data.js +56 -0
- package/src/webmcp/tools/set-field-value.js +71 -0
- package/types/i18n/de.d.ts +0 -19
- package/types/i18n/en.d.ts +0 -19
- package/types/i18n/fr.d.ts +0 -19
- package/types/i18n/nl.d.ts +0 -19
- package/types/i18n/types.d.ts +0 -19
- package/types/i18n/types.d.ts.map +1 -1
- package/types/state/state-tree.d.ts.map +1 -1
- package/types/webmcp/index.d.ts +78 -0
- package/types/webmcp/index.d.ts.map +1 -0
- package/types/webmcp/project.d.ts +59 -0
- package/types/webmcp/project.d.ts.map +1 -0
- package/types/webmcp/resolve.d.ts +11 -0
- package/types/webmcp/resolve.d.ts.map +1 -0
- package/types/webmcp/tools/describe-state.d.ts +70 -0
- package/types/webmcp/tools/describe-state.d.ts.map +1 -0
- package/types/webmcp/tools/fill-form-skill.d.ts +24 -0
- package/types/webmcp/tools/fill-form-skill.d.ts.map +1 -0
- package/types/webmcp/tools/get-data.d.ts +31 -0
- package/types/webmcp/tools/get-data.d.ts.map +1 -0
- package/types/webmcp/tools/get-field-suggestions.d.ts +66 -0
- package/types/webmcp/tools/get-field-suggestions.d.ts.map +1 -0
- package/types/webmcp/tools/set-data.d.ts +65 -0
- package/types/webmcp/tools/set-data.d.ts.map +1 -0
- package/types/webmcp/tools/set-field-value.d.ts +73 -0
- package/types/webmcp/tools/set-field-value.d.ts.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@json-layout/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Compilation and state management utilities for JSON Layout.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -45,6 +45,12 @@
|
|
|
45
45
|
"types": "./types/utils/doc-options.d.ts",
|
|
46
46
|
"default": "./src/utils/doc-options.js"
|
|
47
47
|
}
|
|
48
|
+
},
|
|
49
|
+
"./webmcp": {
|
|
50
|
+
"import": {
|
|
51
|
+
"types": "./types/webmcp/index.d.ts",
|
|
52
|
+
"default": "./src/webmcp/index.js"
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
},
|
|
50
56
|
"files": [
|
|
@@ -89,6 +95,7 @@
|
|
|
89
95
|
"marked": "^15.0.7"
|
|
90
96
|
},
|
|
91
97
|
"devDependencies": {
|
|
98
|
+
"@mcp-b/webmcp-types": "^2.2.0",
|
|
92
99
|
"@types/debug": "^4.1.7",
|
|
93
100
|
"@types/node-fetch": "^2.6.5",
|
|
94
101
|
"json-schema-to-typescript": "^14.0.0",
|
package/src/i18n/de.js
CHANGED
|
@@ -14,25 +14,6 @@ export default {
|
|
|
14
14
|
up: 'Nach oben verschieben',
|
|
15
15
|
down: 'Nach unten verschieben',
|
|
16
16
|
showHelp: 'Hilfemeldung anzeigen',
|
|
17
|
-
mdeLink1: '[Link-Titel',
|
|
18
|
-
mdeLink2: '](Link-URL)',
|
|
19
|
-
mdeImg1: '',
|
|
21
|
-
mdeTable1: '',
|
|
22
|
-
mdeTable2: '\n\n| Spalte 1 | Spalte 2 | Spalte 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n',
|
|
23
|
-
bold: 'Fett',
|
|
24
|
-
italic: 'Kursiv',
|
|
25
|
-
heading: 'Titel',
|
|
26
|
-
quote: 'Zitat',
|
|
27
|
-
unorderedList: 'Ungeordnete Liste',
|
|
28
|
-
orderedList: 'Geordnete Liste',
|
|
29
|
-
createLink: 'Link erstellen',
|
|
30
|
-
insertImage: 'Bild einfügen',
|
|
31
|
-
createTable: 'Tabelle erstellen',
|
|
32
|
-
preview: 'Vorschau',
|
|
33
|
-
mdeGuide: 'Syntax-Dokumentation',
|
|
34
|
-
undo: 'Rückgängig',
|
|
35
|
-
redo: 'Wiederholen',
|
|
36
17
|
default: 'standard: ',
|
|
37
18
|
name: 'name: ',
|
|
38
19
|
examples: 'Beispiele: ',
|
package/src/i18n/en.js
CHANGED
|
@@ -14,25 +14,6 @@ export default {
|
|
|
14
14
|
up: 'Move up',
|
|
15
15
|
down: 'Move down',
|
|
16
16
|
showHelp: 'Show a help message',
|
|
17
|
-
mdeLink1: '[Link title',
|
|
18
|
-
mdeLink2: '](link url)',
|
|
19
|
-
mdeImg1: '',
|
|
21
|
-
mdeTable1: '',
|
|
22
|
-
mdeTable2: '\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n',
|
|
23
|
-
bold: 'Bold',
|
|
24
|
-
italic: 'Italic',
|
|
25
|
-
heading: 'Title',
|
|
26
|
-
quote: 'Quote',
|
|
27
|
-
unorderedList: 'Unordered list',
|
|
28
|
-
orderedList: 'Ordered list',
|
|
29
|
-
createLink: 'Create a link',
|
|
30
|
-
insertImage: 'Insert an image',
|
|
31
|
-
createTable: 'Create a table',
|
|
32
|
-
preview: 'Aperçu du rendu',
|
|
33
|
-
mdeGuide: 'Documentation de la syntaxe',
|
|
34
|
-
undo: 'Undo',
|
|
35
|
-
redo: 'Redo',
|
|
36
17
|
default: 'default: ',
|
|
37
18
|
name: 'name: ',
|
|
38
19
|
examples: 'Examples: ',
|
package/src/i18n/fr.js
CHANGED
|
@@ -14,25 +14,6 @@ export default {
|
|
|
14
14
|
up: 'Décaler vers le haut',
|
|
15
15
|
down: 'Décaler vers le bas',
|
|
16
16
|
showHelp: 'Afficher un message d\'aide',
|
|
17
|
-
mdeLink1: '[titre du lien',
|
|
18
|
-
mdeLink2: '](adresse du lien)',
|
|
19
|
-
mdeImg1: '',
|
|
21
|
-
mdeTable1: '',
|
|
22
|
-
mdeTable2: '\n\n| Colonne 1 | Colonne 2 | Colonne 3 |\n| -------- | -------- | -------- |\n| Texte | Texte | Texte |\n\n',
|
|
23
|
-
bold: 'Gras',
|
|
24
|
-
italic: 'Italique',
|
|
25
|
-
heading: 'Titre',
|
|
26
|
-
quote: 'Citation',
|
|
27
|
-
unorderedList: 'Liste à puce',
|
|
28
|
-
orderedList: 'Liste numérotée',
|
|
29
|
-
createLink: 'Créer un lien',
|
|
30
|
-
insertImage: 'Insérer une image',
|
|
31
|
-
createTable: 'Créer un tableau',
|
|
32
|
-
preview: 'Preview',
|
|
33
|
-
mdeGuide: 'Syntax documentation',
|
|
34
|
-
undo: 'Défaire',
|
|
35
|
-
redo: 'Refaire',
|
|
36
17
|
default: 'défaut : ',
|
|
37
18
|
name: 'nom : ',
|
|
38
19
|
examples: 'Exemples : ',
|
package/src/i18n/nl.js
CHANGED
|
@@ -14,25 +14,6 @@ export default {
|
|
|
14
14
|
up: 'Omhoog verplaatsen',
|
|
15
15
|
down: 'Omlaag verplaatsen',
|
|
16
16
|
showHelp: 'Toon helpbericht',
|
|
17
|
-
mdeLink1: '[Link titel',
|
|
18
|
-
mdeLink2: '](link url)',
|
|
19
|
-
mdeImg1: '',
|
|
21
|
-
mdeTable1: '',
|
|
22
|
-
mdeTable2: '\n\n| Kolom 1 | Kolom 2 | Kolom 3 |\n| -------- | -------- | -------- |\n| Tekst | Tekst | Tekst |\n\n',
|
|
23
|
-
bold: 'Vet',
|
|
24
|
-
italic: 'Cursief',
|
|
25
|
-
heading: 'Titel',
|
|
26
|
-
quote: 'Citaat',
|
|
27
|
-
unorderedList: 'Ongeordende lijst',
|
|
28
|
-
orderedList: 'Geordende lijst',
|
|
29
|
-
createLink: 'Maak een koppeling',
|
|
30
|
-
insertImage: 'Afbeelding invoegen',
|
|
31
|
-
createTable: 'Tabel aanmaken',
|
|
32
|
-
preview: 'Voorbeeld',
|
|
33
|
-
mdeGuide: 'Documentatie over syntaxis',
|
|
34
|
-
undo: 'Ongedaan maken',
|
|
35
|
-
redo: 'Opnieuw',
|
|
36
17
|
default: 'standaard: ',
|
|
37
18
|
name: 'naam: ',
|
|
38
19
|
examples: 'Voorbeelden: ',
|
package/src/i18n/types.ts
CHANGED
|
@@ -18,25 +18,6 @@ export interface StateOptionsMessages {
|
|
|
18
18
|
up: string
|
|
19
19
|
down: string
|
|
20
20
|
showHelp: string
|
|
21
|
-
mdeLink1: string
|
|
22
|
-
mdeLink2: string
|
|
23
|
-
mdeImg1: string
|
|
24
|
-
mdeImg2: string
|
|
25
|
-
mdeTable1: string
|
|
26
|
-
mdeTable2: string
|
|
27
|
-
bold: string
|
|
28
|
-
italic: string
|
|
29
|
-
heading: string
|
|
30
|
-
quote: string
|
|
31
|
-
unorderedList: string
|
|
32
|
-
orderedList: string
|
|
33
|
-
createLink: string
|
|
34
|
-
insertImage: string
|
|
35
|
-
createTable: string
|
|
36
|
-
preview: string
|
|
37
|
-
mdeGuide: string
|
|
38
|
-
undo: string
|
|
39
|
-
redo: string
|
|
40
21
|
keyboardDate: string
|
|
41
22
|
keyboardDateTime: string
|
|
42
23
|
}
|
package/src/state/state-tree.js
CHANGED
|
@@ -51,7 +51,16 @@ export function createStateTree (
|
|
|
51
51
|
) {
|
|
52
52
|
logStateTree('createStateTree', skeleton.root)
|
|
53
53
|
const validate = compiledLayout.validates[skeleton.refPointer]
|
|
54
|
-
|
|
54
|
+
let valid
|
|
55
|
+
if (compiledLayout.options?.ajvOptions?.coerceTypes) {
|
|
56
|
+
const serializedBefore = JSON.stringify(data)
|
|
57
|
+
valid = validate(data)
|
|
58
|
+
if (JSON.stringify(data) !== serializedBefore) {
|
|
59
|
+
data = { .../** @type {Record<string, unknown>} */(data) }
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
valid = validate(data)
|
|
63
|
+
}
|
|
55
64
|
if (validate.errors) {
|
|
56
65
|
logValidation(`${skeleton.root} new state tree initial validation errors`, validate.errors, validationState)
|
|
57
66
|
for (const error of validate.errors) {
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file WebMCP integration for JSON Layout StatefulLayout
|
|
3
|
+
* @description Provides MCP tool descriptors that work with browser's navigator.modelContext
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import debug from 'debug'
|
|
7
|
+
|
|
8
|
+
import * as describeState from './tools/describe-state.js'
|
|
9
|
+
import * as setFieldValue from './tools/set-field-value.js'
|
|
10
|
+
import * as setData from './tools/set-data.js'
|
|
11
|
+
import * as getData from './tools/get-data.js'
|
|
12
|
+
import * as getFieldSuggestions from './tools/get-field-suggestions.js'
|
|
13
|
+
import * as fillFormSkill from './tools/fill-form-skill.js'
|
|
14
|
+
|
|
15
|
+
/** @typedef {import('@mcp-b/webmcp-types').ToolDescriptor} ToolDescriptor */
|
|
16
|
+
|
|
17
|
+
const log = debug('jl:webmcp')
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} WebMCPOptions
|
|
21
|
+
* @property {string} [prefixName] - Prefix for all tool names
|
|
22
|
+
* @property {string} [dataTitle] - Title used in descriptions (default: 'form')
|
|
23
|
+
* @property {object} [schema] - The original JSON schema
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* WebMCP class that provides MCP tool descriptors for a StatefulLayout instance
|
|
28
|
+
*/
|
|
29
|
+
export class WebMCP {
|
|
30
|
+
/**
|
|
31
|
+
* @readonly
|
|
32
|
+
* @type {import('../state/index.js').StatefulLayout}
|
|
33
|
+
*/
|
|
34
|
+
_statefulLayout
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @readonly
|
|
38
|
+
* @type {string}
|
|
39
|
+
*/
|
|
40
|
+
_prefixName
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @readonly
|
|
44
|
+
* @type {string}
|
|
45
|
+
*/
|
|
46
|
+
_dataTitle
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @readonly
|
|
50
|
+
* @type {string}
|
|
51
|
+
*/
|
|
52
|
+
_skill
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @readonly
|
|
56
|
+
* @type {object | null}
|
|
57
|
+
*/
|
|
58
|
+
_schema = null
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @type {string[]}
|
|
62
|
+
*/
|
|
63
|
+
_registeredTools = []
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {import('../state/index.js').StatefulLayout} statefulLayout
|
|
67
|
+
* @param {WebMCPOptions} [options]
|
|
68
|
+
*/
|
|
69
|
+
constructor (statefulLayout, options = {}) {
|
|
70
|
+
this._statefulLayout = statefulLayout
|
|
71
|
+
this._prefixName = options.prefixName || ''
|
|
72
|
+
this._dataTitle = options.dataTitle || 'form'
|
|
73
|
+
this._schema = options.schema || null
|
|
74
|
+
this._skill = fillFormSkill.generateSkill(this._dataTitle, this._prefixName, !!this._schema, this._statefulLayout)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} name
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
_toolName (name) {
|
|
82
|
+
return this._prefixName + name
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @returns {ToolDescriptor[]}
|
|
87
|
+
*/
|
|
88
|
+
getTools () {
|
|
89
|
+
const dataTitle = this._dataTitle
|
|
90
|
+
|
|
91
|
+
/** @type {ToolDescriptor[]} */
|
|
92
|
+
const tools = [
|
|
93
|
+
{
|
|
94
|
+
name: this._toolName('fillFormSkill'),
|
|
95
|
+
description: fillFormSkill.getDescription(dataTitle),
|
|
96
|
+
outputSchema: { type: 'string' },
|
|
97
|
+
execute: async (args) => {
|
|
98
|
+
try {
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: 'text', text: this._skill }]
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
106
|
+
isError: true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: this._toolName('getData'),
|
|
113
|
+
description: getData.getDescription(dataTitle),
|
|
114
|
+
inputSchema: getData.inputSchema,
|
|
115
|
+
outputSchema: getData.outputSchema,
|
|
116
|
+
execute: async (args) => {
|
|
117
|
+
try {
|
|
118
|
+
const result = getData.execute(this._statefulLayout, args || {})
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
124
|
+
return {
|
|
125
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
126
|
+
isError: true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: this._toolName('setData'),
|
|
133
|
+
description: setData.getDescription(dataTitle),
|
|
134
|
+
inputSchema: setData.inputSchema,
|
|
135
|
+
outputSchema: setData.outputSchema,
|
|
136
|
+
execute: async (args) => {
|
|
137
|
+
try {
|
|
138
|
+
if (!args?.data) {
|
|
139
|
+
throw new Error('data is required')
|
|
140
|
+
}
|
|
141
|
+
const result = setData.execute(
|
|
142
|
+
this._statefulLayout,
|
|
143
|
+
/** @type {{ data: unknown }} */(args)
|
|
144
|
+
)
|
|
145
|
+
return {
|
|
146
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
147
|
+
}
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
152
|
+
isError: true
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: this._toolName('describeState'),
|
|
159
|
+
description: describeState.getDescription(dataTitle),
|
|
160
|
+
inputSchema: describeState.inputSchema,
|
|
161
|
+
outputSchema: describeState.outputSchema,
|
|
162
|
+
execute: async (args) => {
|
|
163
|
+
try {
|
|
164
|
+
const result = describeState.execute(this._statefulLayout, args || {})
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
170
|
+
return {
|
|
171
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
172
|
+
isError: true
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: this._toolName('setFieldValue'),
|
|
179
|
+
description: setFieldValue.getDescription(dataTitle),
|
|
180
|
+
inputSchema: setFieldValue.inputSchema,
|
|
181
|
+
outputSchema: setFieldValue.outputSchema,
|
|
182
|
+
execute: async (args) => {
|
|
183
|
+
try {
|
|
184
|
+
if (!args?.path) {
|
|
185
|
+
throw new Error('path is required')
|
|
186
|
+
}
|
|
187
|
+
const result = setFieldValue.execute(
|
|
188
|
+
this._statefulLayout,
|
|
189
|
+
/** @type {{ path: string, value: unknown }} */(args)
|
|
190
|
+
)
|
|
191
|
+
return {
|
|
192
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
196
|
+
return {
|
|
197
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
198
|
+
isError: true
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: this._toolName('getFieldSuggestions'),
|
|
205
|
+
description: getFieldSuggestions.getDescription(dataTitle),
|
|
206
|
+
inputSchema: getFieldSuggestions.inputSchema,
|
|
207
|
+
outputSchema: getFieldSuggestions.outputSchema,
|
|
208
|
+
execute: async (args) => {
|
|
209
|
+
try {
|
|
210
|
+
if (!args?.path) {
|
|
211
|
+
throw new Error('path is required')
|
|
212
|
+
}
|
|
213
|
+
const result = await getFieldSuggestions.execute(
|
|
214
|
+
this._statefulLayout,
|
|
215
|
+
/** @type {{ path: string, query?: string }} */(args)
|
|
216
|
+
)
|
|
217
|
+
return {
|
|
218
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
224
|
+
isError: true
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
if (this._schema) {
|
|
232
|
+
tools.push({
|
|
233
|
+
name: this._toolName('getSchema'),
|
|
234
|
+
description: `Get the the JSON schema that governs "${dataTitle}" form.`,
|
|
235
|
+
outputSchema: {
|
|
236
|
+
type: 'object',
|
|
237
|
+
properties: {
|
|
238
|
+
jsonSchema: {},
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
execute: async (args) => {
|
|
242
|
+
return {
|
|
243
|
+
jsonSchema: this._schema
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return tools
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @returns {Promise<void>}
|
|
254
|
+
*/
|
|
255
|
+
async registerTools () {
|
|
256
|
+
if (typeof navigator === 'undefined' || !navigator.modelContext) {
|
|
257
|
+
throw new Error('navigator.modelContext is not available')
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const tools = this.getTools()
|
|
261
|
+
for (const tool of tools) {
|
|
262
|
+
log('registering tool:', tool.name)
|
|
263
|
+
// @ts-ignore - complex generic types from webmcp-types
|
|
264
|
+
await navigator.modelContext.registerTool(tool)
|
|
265
|
+
this._registeredTools.push(tool.name)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @returns {Promise<void>}
|
|
271
|
+
*/
|
|
272
|
+
async unregisterTools () {
|
|
273
|
+
if (typeof navigator === 'undefined' || !navigator.modelContext) {
|
|
274
|
+
throw new Error('navigator.modelContext is not available')
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const name of this._registeredTools) {
|
|
278
|
+
log('unregistering tool:', name)
|
|
279
|
+
await navigator.modelContext.unregisterTool(name)
|
|
280
|
+
}
|
|
281
|
+
this._registeredTools = []
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Projection functions for webmcp tools
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isItemsLayout } from '@json-layout/vocabulary'
|
|
6
|
+
|
|
7
|
+
const constraintKeys = {
|
|
8
|
+
'number-field': ['min', 'max', 'step', 'precision'],
|
|
9
|
+
slider: ['min', 'max', 'step'],
|
|
10
|
+
'date-picker': ['min', 'max', 'format'],
|
|
11
|
+
'date-time-picker': ['min', 'max'],
|
|
12
|
+
'time-picker': ['min', 'max'],
|
|
13
|
+
combobox: ['separator'],
|
|
14
|
+
'number-combobox': ['separator']
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} comp
|
|
19
|
+
* @returns {string[]|undefined}
|
|
20
|
+
*/
|
|
21
|
+
function getConstraintKeys (comp) {
|
|
22
|
+
// @ts-ignore - complex union type not fully represented
|
|
23
|
+
return constraintKeys[comp]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {import('../state/types.js').StateNode} node
|
|
28
|
+
* @param {import('../state/index.js').StatefulLayout} statefulLayout
|
|
29
|
+
* @returns {{
|
|
30
|
+
* key: string|number,
|
|
31
|
+
* path: string,
|
|
32
|
+
* comp: string,
|
|
33
|
+
* data: unknown,
|
|
34
|
+
* title?: string,
|
|
35
|
+
* label?: string,
|
|
36
|
+
* help?: string,
|
|
37
|
+
* error?: string,
|
|
38
|
+
* childError?: boolean,
|
|
39
|
+
* required?: boolean,
|
|
40
|
+
* readOnly?: boolean,
|
|
41
|
+
* constraints?: Record<string, unknown>,
|
|
42
|
+
* oneOfItems?: Array<{key: number, title: string}>,
|
|
43
|
+
* children?: Array<any>
|
|
44
|
+
* getSuffections?: boolean
|
|
45
|
+
* }}
|
|
46
|
+
*/
|
|
47
|
+
export function projectNode (node, statefulLayout) {
|
|
48
|
+
/**
|
|
49
|
+
* @type {{
|
|
50
|
+
* key: string|number,
|
|
51
|
+
* path: string,
|
|
52
|
+
* comp: string,
|
|
53
|
+
* data: unknown,
|
|
54
|
+
* title?: string,
|
|
55
|
+
* label?: string,
|
|
56
|
+
* help?: string,
|
|
57
|
+
* error?: string,
|
|
58
|
+
* childError?: boolean,
|
|
59
|
+
* required?: boolean,
|
|
60
|
+
* readOnly?: boolean,
|
|
61
|
+
* constraints?: Record<string, unknown>,
|
|
62
|
+
* oneOfItems?: Array<{key: number, title: string}>,
|
|
63
|
+
* children?: Array<any>
|
|
64
|
+
* getSuggestions?: boolean
|
|
65
|
+
* }}
|
|
66
|
+
*/
|
|
67
|
+
const out = {
|
|
68
|
+
key: node.key,
|
|
69
|
+
path: node.fullKey,
|
|
70
|
+
comp: node.layout.comp,
|
|
71
|
+
data: node.data
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const layout = /** @type {Record<string, unknown>} */(node.layout)
|
|
75
|
+
if (typeof layout.title === 'string') out.title = layout.title
|
|
76
|
+
if (typeof layout.label === 'string') out.label = layout.label
|
|
77
|
+
if (node.layout.help) out.help = node.layout.help
|
|
78
|
+
|
|
79
|
+
if (node.error) out.error = node.error
|
|
80
|
+
if (node.childError) out.childError = true
|
|
81
|
+
|
|
82
|
+
if (node.skeleton.required) out.required = true
|
|
83
|
+
if (node.options.readOnly) out.readOnly = true
|
|
84
|
+
if (isItemsLayout(node.layout, statefulLayout.compiledLayout.components)) out.getSuggestions = true
|
|
85
|
+
|
|
86
|
+
const keys = getConstraintKeys(node.layout.comp)
|
|
87
|
+
if (keys) {
|
|
88
|
+
/** @type {Record<string, unknown>} */
|
|
89
|
+
const constraints = {}
|
|
90
|
+
for (const k of keys) {
|
|
91
|
+
const v = layout[k]
|
|
92
|
+
if (v !== undefined && v !== null) constraints[k] = v
|
|
93
|
+
}
|
|
94
|
+
if (Object.keys(constraints).length > 0) out.constraints = constraints
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (node.layout.comp === 'one-of-select' && Array.isArray(layout.oneOfItems)) {
|
|
98
|
+
out.oneOfItems = layout.oneOfItems
|
|
99
|
+
.filter((item) => !item.header)
|
|
100
|
+
.map((item) => ({ key: item.key, title: item.title }))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (node.children) {
|
|
104
|
+
out.children = node.children
|
|
105
|
+
.filter((c) => c.layout.comp !== 'none')
|
|
106
|
+
.map(node => projectNode(node, statefulLayout))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return out
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {import('../state/types.js').StateTree} stateTree
|
|
114
|
+
* @param {import('../state/index.js').StatefulLayout} statefulLayout
|
|
115
|
+
* @returns {{ root: ReturnType<typeof projectNode>, valid: boolean }}
|
|
116
|
+
*/
|
|
117
|
+
export function projectStateTree (stateTree, statefulLayout) {
|
|
118
|
+
return {
|
|
119
|
+
root: projectNode(stateTree.root, statefulLayout),
|
|
120
|
+
valid: stateTree.valid
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {import('../state/types.js').StateNode} node
|
|
126
|
+
* @returns {Array<{path: string, message: string}>}
|
|
127
|
+
*/
|
|
128
|
+
export function collectErrors (node) {
|
|
129
|
+
/** @type {Array<{path: string, message: string}>} */
|
|
130
|
+
const errors = []
|
|
131
|
+
collectErrorsRecurse(node, errors)
|
|
132
|
+
return errors
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {import('../state/types.js').StateNode} node
|
|
137
|
+
* @param {Array<{path: string, message: string}>} errors
|
|
138
|
+
*/
|
|
139
|
+
function collectErrorsRecurse (node, errors) {
|
|
140
|
+
if (node.error) {
|
|
141
|
+
errors.push({ path: node.fullKey, message: node.error })
|
|
142
|
+
}
|
|
143
|
+
if (node.children) {
|
|
144
|
+
for (const child of node.children) {
|
|
145
|
+
collectErrorsRecurse(child, errors)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Node resolution for webmcp tools
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Navigate from a root StateNode to a descendant node by path.
|
|
7
|
+
* @param {import('../state/types.js').StateNode} root
|
|
8
|
+
* @param {string} path
|
|
9
|
+
* @returns {import('../state/types.js').StateNode|undefined}
|
|
10
|
+
*/
|
|
11
|
+
export function resolveNode (root, path) {
|
|
12
|
+
if (!path || path === '/') return root
|
|
13
|
+
|
|
14
|
+
const segments = path.replace(/^\//, '').split('/')
|
|
15
|
+
/** @type {import('../state/types.js').StateNode|undefined} */
|
|
16
|
+
let current = root
|
|
17
|
+
|
|
18
|
+
for (const segment of segments) {
|
|
19
|
+
if (!current?.children) return undefined
|
|
20
|
+
const key = /^\d+$/.test(segment) ? parseInt(segment, 10) : segment
|
|
21
|
+
current = current.children.find((c) => c.key === key)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return current
|
|
25
|
+
}
|