@formio/uag 1.6.0 → 1.8.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 +10 -9
- package/lib/UAGFormInterface.d.ts +7 -0
- package/lib/UAGFormInterface.js +29 -3
- package/lib/templates/allFieldsCollected.md +3 -2
- package/lib/templates/getFormFieldsEmpty.md +2 -2
- package/lib/tools/confirmSubmission.js +1 -1
- package/lib/tools/submitForm.js +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# The Form.io
|
|
1
|
+
# The Form.io Universal Agent Gateway (UAG)
|
|
2
2
|
The Universal Agent Gateway (UAG) introduces the power of [Form.io](https://form.io) forms to AI agents.
|
|
3
3
|
|
|
4
4
|
The Form.io UAG uses the [**Model Context Protocol (MCP)**](https://modelcontextprotocol.io/docs/getting-started/intro) to enable Form.io interaction through an AI agent workflow. By providing AI agents with the **dynamic context** of how to use Form.io JSON forms, the UAG allows a user to interact with any aspect of their enterprise system served by the Form.io Platform directly through their AI agent.
|
|
@@ -7,7 +7,7 @@ The Form.io UAG uses the [**Model Context Protocol (MCP)**](https://modelcontext
|
|
|
7
7
|
|
|
8
8
|
## Table of Contents
|
|
9
9
|
|
|
10
|
-
- [The Form.io
|
|
10
|
+
- [The Form.io Universal Agent Gateway (UAG)](#the-formio-universal-agent-gateway-uag)
|
|
11
11
|
- [Introduction](#introduction)
|
|
12
12
|
- [The Model Context Protocol (MCP)](#the-model-context-protocol-mcp)
|
|
13
13
|
- [Dynamic Context](#dynamic-context)
|
|
@@ -27,7 +27,7 @@ The Form.io UAG uses the [**Model Context Protocol (MCP)**](https://modelcontext
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
29
|
## Introduction
|
|
30
|
-
The core purpose of the UAG is to equip AI agents with the ability to
|
|
30
|
+
The core purpose of the UAG is to equip AI agents with the ability to forward a user's inputs to the Form.io platform. It allows the AI agent to apply natural language requests, scanned documents, and any other input it can parse into Form.io functionality, as defined by selected forms and fields.
|
|
31
31
|
|
|
32
32
|
### The Model Context Protocol (MCP)
|
|
33
33
|
The Model Context Protocol (MCP) is an open-source standard for connecting AI applications to external systems.
|
|
@@ -37,7 +37,7 @@ The core purpose of the UAG is to equip AI agents with the ability to fluidly ap
|
|
|
37
37
|
### Dynamic Context
|
|
38
38
|
What does it mean to say MCP provides a dynamic context for how to use these tools?
|
|
39
39
|
|
|
40
|
-
The power of AI agents is their ability to parse natural language and infer a user's intent rather than take rote input like a command line. The dynamic context delivered by MCP gives the AI agent guidance on how to correlate the user's prompt to the tools and data available. When the AI agent receives a prompt, it uses this dynamic context to determine what tool it should use, what elements of the prompt are inputs to that tool, and what additional input might be necessary.
|
|
40
|
+
The power of AI agents is their ability to parse free-form inputs, like natural language, and infer a user's intent rather than take rote input like a command line. The dynamic context delivered by MCP gives the AI agent guidance on how to correlate the user's prompt to the tools and data available within Form.io. When the AI agent receives a prompt, it uses this dynamic context to determine what tool it should use, what elements of the prompt are inputs to that tool, and what additional input might be necessary.
|
|
41
41
|
|
|
42
42
|
### The Role of the UAG
|
|
43
43
|
The UAG is the package that contains everything sitting between the Form.io Platform and the AI agent.
|
|
@@ -47,9 +47,11 @@ The core purpose of the UAG is to equip AI agents with the ability to fluidly ap
|
|
|
47
47
|
Form.io's drag-and-drop Form Builder simultaneously defines the look of the form and the structure of the data. This makes it easy to use data collected through Form.io forms elsewhere in an application or as an input to other enterprise systems.
|
|
48
48
|

|
|
49
49
|
|
|
50
|
-
When the UAG is able to map the
|
|
50
|
+
When the UAG is able to map the freeform inputs that an AI agent receives to the data model defined by a form, it means that data can be quickly supplied to the enterprise tools that depend on the Form.io platform without additional interpretation or transformation.
|
|
51
51
|
This allows the UAG and Form.io to serve as a reliable middleware between an AI agent and countless other systems.
|
|
52
52
|
|
|
53
|
+
The same role-based access control that governs all Form.io forms and submissions still applies to any interaction through the UAG. This addresses many potential issues some users may have with connecting AI agents to enterprise systems.
|
|
54
|
+
|
|
53
55
|
Here is a visual graphic of the layers provided by the UAG to achieve a trusted and deterministic interface between AI Agents to external systems through the UAG + Form.io Server.
|
|
54
56
|
|
|
55
57
|

|
|
@@ -79,8 +81,8 @@ The following tools provided by the UAG can be described as follows:
|
|
|
79
81
|
| get_form_fields | When the AI agent infers the user intends to use a specific form, this tool provides the agent with a high level overview of all the fields needed (along with the field data path) to submit that form. |
|
|
80
82
|
| get_field_info | Once the fields have been determined using `get_form_fields`, this tool provides specific information about the requested fields, such as validation, conditionals, input formats, etc. <br/>This tool instructs the AI agent on how to format and structure the data that is sent to the MCP server. |
|
|
81
83
|
| collect_field_data | Provides the AI agent with a mechanism to dynamically collect the required information from the user. It can parse the user's input into multiple fields at once, and will identify additional inputs from the user if needed. <br/>This tool supports complex and structured data collection and is compatible with nested or multi-value fields like nested forms, data grids, etc. |
|
|
82
|
-
| confirm_form_submission
|
|
83
|
-
| submit_completed_form
|
|
84
|
+
| confirm_form_submission | This tool is used to provide a summary of all data collected before a submission is made to the form. |
|
|
85
|
+
| submit_completed_form | Provides the AI agent with the ability to submit all of the data collected from the user to create the form submission. |
|
|
84
86
|
| find_submissions | Enables the agent to parse a user's natural language request into a query for a submission, or a specific field of a particular submission. |
|
|
85
87
|
| submission_update | Provides the AI agent with the ability to update an existing submission, either by supplying unfilled fields or updating existing ones if allowed. Provides the AI agent with the context of the existing field values, allowing inline changes or edits. |
|
|
86
88
|
|
|
@@ -277,11 +279,10 @@ This module can be configured in many ways. One of those ways is through the use
|
|
|
277
279
|
| DEBUG | Variable used to perform debug logs of server activity | formio.* |
|
|
278
280
|
| PORTAL_SECRET | Enterprise Only: Allows you to connect to the UAG from the Form.io Enterprise Portal. | CHANGEME |
|
|
279
281
|
| JWT_SECRET | A secret used to generate and validate JWT tokens generated through the authentication process of the UAG. This does not need to match the JWT_SECRET of the Enterprise Server that it is connected to. | CHANGEME |
|
|
280
|
-
| PORTAL_SECRET | (Enterprise Only) Used to connect the UAG server with the Enterprise Portal | CHANGEME |
|
|
281
282
|
| JWT_EXPIRE_TIME | The expiration for the jwt secret. | 3600 |
|
|
282
283
|
| MONGO | (Enterprise Only) Allows you to connect the UAG directly to a mongo database, rather than having to redirect the submissions to the Form.io Submission APIs. | |
|
|
283
284
|
| MONGO_CONFIG | JSON configuration for the Node.js Mongo Driver. | |
|
|
284
|
-
| BASE_URL | The public URL that the UAG is hosted on. This allows for proper OIDC authentication and allows for the authentication callbacks to point to the correct url. | https://
|
|
285
|
+
| BASE_URL | The public URL that the UAG is hosted on. This allows for proper OIDC authentication and allows for the authentication callbacks to point to the correct url. | https://forms.yoursite.com |
|
|
285
286
|
| LOGIN_FORM | The public URL to the Login Form JSON endpoint. | https://mysite.com/project/user/login |
|
|
286
287
|
| CORS | The cors domain, or the JSON configuration to configure the "cors" node.js module cross domain resource sharing. | *.* |
|
|
287
288
|
|
|
@@ -76,6 +76,13 @@ export declare class UAGFormInterface extends FormInterface {
|
|
|
76
76
|
* @returns { boolean } - True if the component is an input component, false otherwise.
|
|
77
77
|
*/
|
|
78
78
|
inputComponent(component: Component): boolean;
|
|
79
|
+
getEmptyValue(component: Component): {}[] | {
|
|
80
|
+
data: {};
|
|
81
|
+
}[] | {
|
|
82
|
+
data: {};
|
|
83
|
+
} | {
|
|
84
|
+
data?: undefined;
|
|
85
|
+
} | null;
|
|
79
86
|
/**
|
|
80
87
|
* Get the relevant fields from the current form. This will return any non-nested input components whose
|
|
81
88
|
* values have not already been set within the data model. This allows the agent to know what fields still need to be
|
package/lib/UAGFormInterface.js
CHANGED
|
@@ -223,6 +223,22 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
223
223
|
}
|
|
224
224
|
return true;
|
|
225
225
|
}
|
|
226
|
+
getEmptyValue(component) {
|
|
227
|
+
const modelType = core_1.Utils.getModelType(component);
|
|
228
|
+
switch (modelType) {
|
|
229
|
+
case 'nestedArray':
|
|
230
|
+
return [{}];
|
|
231
|
+
case 'nestedDataArray':
|
|
232
|
+
return [{ data: {} }];
|
|
233
|
+
case 'dataObject':
|
|
234
|
+
return { data: {} };
|
|
235
|
+
case 'object':
|
|
236
|
+
case 'map':
|
|
237
|
+
return {};
|
|
238
|
+
default:
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
226
242
|
/**
|
|
227
243
|
* Get the relevant fields from the current form. This will return any non-nested input components whose
|
|
228
244
|
* values have not already been set within the data model. This allows the agent to know what fields still need to be
|
|
@@ -255,8 +271,16 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
255
271
|
name: 'getFields',
|
|
256
272
|
shouldProcess: () => true,
|
|
257
273
|
process: async (context) => {
|
|
258
|
-
const { component, path, value } = context;
|
|
274
|
+
const { component, path, value, data } = context;
|
|
259
275
|
if (this.isNestedComponent(component)) {
|
|
276
|
+
// For nested components, we need to always have a value so that the child components
|
|
277
|
+
// are added to the list of fields needing data.
|
|
278
|
+
if (core_1.Utils.isComponentDataEmpty(component, data, path)) {
|
|
279
|
+
const emptyValue = this.getEmptyValue(component);
|
|
280
|
+
if (emptyValue !== null) {
|
|
281
|
+
(0, lodash_1.set)(data, path, emptyValue);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
260
284
|
nestedPaths.push(path);
|
|
261
285
|
}
|
|
262
286
|
},
|
|
@@ -301,8 +325,10 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
301
325
|
if (component.validate?.required) {
|
|
302
326
|
fieldInfo.totalRequired++;
|
|
303
327
|
}
|
|
304
|
-
// If the
|
|
305
|
-
|
|
328
|
+
// If the value is not empty, then we can skip this field.
|
|
329
|
+
const emptyValue = this.getEmptyValue(component);
|
|
330
|
+
const hasValue = emptyValue === null ? (0, core_1.componentHasValue)(component, value) : !(0, lodash_1.isEqual)(value, emptyValue);
|
|
331
|
+
if (hasValue) {
|
|
306
332
|
if (component.validate?.required) {
|
|
307
333
|
fieldInfo.totalRequiredCollected++;
|
|
308
334
|
}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
4. If they do not wish to "Add Another", then use the `get_form_fields` tool to determine if there are any more fields that need to be collected outside of the **<%= parent.label %>** component. If so, use the `collect_field_data` tool to collect that information (make sure to change the `parent_path` parameter to the level above the **<%= parent.label %>** field, or leave empty if we are at the root of the form).
|
|
10
10
|
<% } else if (parent && (parent.isForm || parent.isContainer)) { %>
|
|
11
11
|
1. Use the `get_form_fields` tool with `parent_path`="<%= parent.data_path %>" and `criteria`="optional" to check if there are any optional fields. If so, ask the user if they wish to fill any of them out.
|
|
12
|
-
2. If there are no optional fields, or the user
|
|
12
|
+
2. If there are no optional fields, or the user does not wish to add any more values to the <%= parent.label %> component, then use the `get_form_fields` tool to determine if there are any more fields that need to be collected outside of the <%= parent.label %> component. If so, use the `collect_field_data` tool to collect that information (make sure to change the parent parameter to the level above the <%= parent.label %> field, or leave empty if we are at the root of the form).
|
|
13
13
|
<% } else { %>
|
|
14
14
|
1. Use the `get_form_fields` tool with `criteria`="optional" to check if there are optional fields. If so, ask the user if they wish to fill any of them out.
|
|
15
|
-
2. If there are no optional fields
|
|
15
|
+
2. If there are no optional fields then use the `submit_completed_form` tool to submit the data. If necessary, use the `confirm_form_submission` tool to provide them a summary of the collected values before submission.
|
|
16
|
+
3. If there are optional fields, but the user does not want to add anything else, use the `submit_completed_form` tool to submit the data. If necessary, use the `confirm_form_submission` tool to provide them a summary of the collected values.<% } %>
|
|
@@ -6,9 +6,9 @@ No fields were found matching `criteria`="<%= criteria %>" for <%= parentLabel %
|
|
|
6
6
|
<% } else if (parent) { %>
|
|
7
7
|
- Considering the search was made using a `parent_path` context, try the search again at a higher `parent_path` context.
|
|
8
8
|
<% } else if (criteria === "optional") { %>
|
|
9
|
-
- Since there are no more "optional" fields, use the `confirm_form_submission` tool to ensure all the collected information is correct.
|
|
9
|
+
- Since there are no more "optional" fields, use the `submit_completed_form` tool to submit all the information if the user confirms they wish to do so, if necessary use the `confirm_form_submission` tool to ensure all the collected information is correct.
|
|
10
10
|
<% } else if (criteria === "required") { %>
|
|
11
11
|
- Use the `get_form_fields` tool, with `criteria`="optional" to determine if there are any optional fields they wish to provide information for.
|
|
12
12
|
<% } else { %>
|
|
13
|
-
- Use the `confirm_form_submission` tool to ensure all the collected information is correct.
|
|
13
|
+
- Use the `submit_completed_form` tool to submit all the information if the user confirms they wish to do so, if necessary use the `confirm_form_submission` tool to ensure all the collected information is correct.
|
|
14
14
|
<% } %>
|
|
@@ -8,7 +8,7 @@ const confirmSubmission = async (project) => {
|
|
|
8
8
|
return (0, lodash_1.defaultsDeep)(project.config?.toolOverrides?.confirm_form_submission || {}, {
|
|
9
9
|
name: 'confirm_form_submission',
|
|
10
10
|
title: 'Confirm Form Submission',
|
|
11
|
-
description: 'Show a summary of the collected form data
|
|
11
|
+
description: 'Show a summary of the collected form data if the user wishes to see a summary of the collected data',
|
|
12
12
|
inputSchema: (new SchemaBuilder_1.SchemaBuilder(project))
|
|
13
13
|
.form_name()
|
|
14
14
|
.form_data().schema,
|
package/lib/tools/submitForm.js
CHANGED
|
@@ -9,7 +9,7 @@ const submitCompletedForm = async (project) => {
|
|
|
9
9
|
return (0, lodash_1.defaultsDeep)(project.config?.toolOverrides?.submit_completed_form || {}, {
|
|
10
10
|
name: 'submit_completed_form',
|
|
11
11
|
title: 'Submit Completed Form',
|
|
12
|
-
description: 'Submit the completed form data
|
|
12
|
+
description: 'Submit the completed form data. Should only be used once all the required fields have been collected, and the user has explicitly confirmed submission (e.g. has said "submit", "send", "done", etc)',
|
|
13
13
|
inputSchema: (new SchemaBuilder_1.SchemaBuilder(project))
|
|
14
14
|
.form_name()
|
|
15
15
|
.form_data().schema,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formio/uag",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "The Form.io Universal Agent Gateway (UAG).",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"clean": "rm -rf lib",
|
|
15
|
-
"build:ts": "tsc",
|
|
15
|
+
"build:ts": "npx tsc",
|
|
16
16
|
"build:copy-templates": "mkdir -p lib/templates && cp src/templates/*.md lib/templates/",
|
|
17
17
|
"build": "npm run clean && npm run build:ts && npm run build:copy-templates",
|
|
18
18
|
"build:docker": "npm run build && docker build -t formio/uag --platform=linux/amd64 .",
|
|
@@ -23,26 +23,26 @@
|
|
|
23
23
|
"author": "",
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@formio/appserver": "^2.
|
|
26
|
+
"@formio/appserver": "^2.8.0",
|
|
27
27
|
"@formio/core": "2.5.1-dev.291.6557e4e",
|
|
28
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
29
29
|
"cors": "^2.8.5",
|
|
30
30
|
"debug": "^4.4.1",
|
|
31
31
|
"dotenv": "^17.2.1",
|
|
32
|
-
"express": "^5.1
|
|
32
|
+
"express": "^5.2.1",
|
|
33
33
|
"jsonwebtoken": "^9.0.2",
|
|
34
34
|
"lodash": "^4.17.21",
|
|
35
35
|
"node-cache": "^5.1.2",
|
|
36
|
-
"zod": "^
|
|
36
|
+
"zod": "^4.1.13"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/chai": "^5.2.3",
|
|
40
40
|
"@types/cors": "^2.8.19",
|
|
41
41
|
"@types/debug": "^4.1.12",
|
|
42
42
|
"@types/dotenv": "^8.2.3",
|
|
43
|
-
"@types/express": "^5.0.
|
|
43
|
+
"@types/express": "^5.0.6",
|
|
44
44
|
"@types/jsonwebtoken": "^9.0.10",
|
|
45
|
-
"@types/lodash": "^4.17.
|
|
45
|
+
"@types/lodash": "^4.17.21",
|
|
46
46
|
"@types/mocha": "^10.0.10",
|
|
47
47
|
"@types/node": "^24.10.1",
|
|
48
48
|
"@types/supertest": "^6.0.3",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"supertest": "^7.1.4",
|
|
52
52
|
"terser": "^5.44.1",
|
|
53
53
|
"ts-node": "^10.9.2",
|
|
54
|
-
"tsx": "^4.
|
|
54
|
+
"tsx": "^4.21.0",
|
|
55
55
|
"typescript": "^5.9.3"
|
|
56
56
|
}
|
|
57
57
|
}
|