@formio/uag 1.12.0 → 1.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -23
- package/lib/tools/fetchExternalData.js +2 -1
- package/lib/tools/findSubmission.js +18 -3
- package/lib/tools/utils.d.ts +20 -0
- package/lib/tools/utils.js +23 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ For the latest documentation, release information, and guides, always refer to t
|
|
|
15
15
|
--------------------------
|
|
16
16
|
There are two primary scenarios that the UAG enables:
|
|
17
17
|
|
|
18
|
-
- **Unstructured
|
|
18
|
+
- **Unstructured 'chat' input to deterministic form submission**: This capability uses the JSON schema of a Form.io form to produce a deterministic structured data object provided natural language conversational input.
|
|
19
19
|
|
|
20
20
|

|
|
21
21
|
|
|
@@ -27,19 +27,43 @@ There are two primary scenarios that the UAG enables:
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
One of the many benefits that the UAG has to offer is the ability to inject **Dynamic Context** into an AI processing cycle. This is achieved from the dynamic nature of the Form JSON schemas that can easily be modified and deployed to any
|
|
30
|
+
One of the many benefits that the UAG has to offer is the ability to inject **Dynamic Context** into an AI processing cycle. This is achieved from the dynamic nature of the Form JSON schemas that can easily be modified and deployed to any environment without the need to update your application or re-train the agents within the process. Because of this, dynamic context changes can be injected seamlessly into an existing running process which will automatically update the behaviors of the Agents utilizing the UAG.
|
|
31
31
|
|
|
32
32
|
## Try it out!
|
|
33
|
-
|
|
33
|
+
There are three working examples in this repo, each spinning up a Form.io server alongside the UAG with Docker Compose. Pick the one that matches what you are building.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
| Example | Pattern | Licensing |
|
|
36
|
+
|---------|---------|-----------|
|
|
37
|
+
| **[Custom Module](examples/custom-module)** | **Conversational, existing client.** Connect Claude Desktop to your forms and talk to them: "I would like to add a new customer." Built as a custom module, so roles and permissions decide what the agent may do. | Open Source — free |
|
|
38
|
+
| **[Conversation Form](examples/conversation-form)** | **Conversational, your own UI.** A chat application whose questions come entirely from a form. About 200 lines, no field names in the code — edit the form and the conversation changes. | Open Source — free |
|
|
39
|
+
| **[Agentic Workflow](examples/agentic-workflow)** | **Autonomous.** A submission triggers an agent that scores it against a rubric and writes its decision back, which in turn triggers a second agent. No human, no chat window. | Enterprise — the trigger needs a Webhook action that can set headers and transform its payload |
|
|
40
|
+
|
|
41
|
+
There is also a [Flow Viewer](examples/flow-viewer) — a small proxy that shows every leg of an agentic run live, which is the fastest way to see what an agent is actually doing.
|
|
42
|
+
|
|
43
|
+
**[Conversation »](examples/conversation-form)** | **[Claude Desktop »](examples/custom-module)** | **[Autonomous »](examples/agentic-workflow)**
|
|
44
|
+
|
|
45
|
+
## Documentation in this repository
|
|
46
|
+
|
|
47
|
+
This file is the main reference: what the UAG is, how to configure a form for it, how to deploy it, and how to troubleshoot it. The rest of the documentation lives next to the code it describes.
|
|
48
|
+
|
|
49
|
+
| Document | What it covers |
|
|
50
|
+
|----------|----------------|
|
|
51
|
+
| **README.md** (this file) | Concepts, [agentic workflow setup](#agentic-workflows), the [MCP tools](#pre-defined-mcp-tools-providing-dynamic-context), [deployment](#deploying-uag), [environment variables](#environment-variables), and [troubleshooting](#troubleshooting). |
|
|
52
|
+
| [module/Readme.md](./module/Readme.md) | Building a **custom module** — adding your own MCP tools, form actions, pre-defined forms and resources, and configuration overrides, then mounting it into the Docker container. |
|
|
53
|
+
| [integrations/claude/Readme.md](./integrations/claude/Readme.md) | The **Claude integration**: its API endpoints and commands, how to run it with Node or Docker, its environment variables, and how to trigger it from a Form.io Webhook action. |
|
|
54
|
+
| [examples/custom-module/Readme.md](./examples/custom-module/Readme.md) | Example — a custom module driven conversationally from **Claude Desktop**, where roles and permissions govern what the agent may do. |
|
|
55
|
+
| [examples/conversation-form/Readme.md](./examples/conversation-form/Readme.md) | Example — a **chat application of your own** whose questions come entirely from a form. |
|
|
56
|
+
| [examples/agentic-workflow/Readme.md](./examples/agentic-workflow/Readme.md) | Example — an **autonomous, webhook-triggered workflow** with two chained agent personas. |
|
|
57
|
+
| [examples/flow-viewer/Readme.md](./examples/flow-viewer/Readme.md) | A small proxy that shows **every leg of an agentic run live** — the token request, each tool call, and the write-back. The quickest way to see what an agent is doing. |
|
|
58
|
+
| [examples/custom-module/module/templates/Readme.md](./examples/custom-module/module/templates/Readme.md) | Overriding the **response templates** the UAG returns to the agent, using Lodash templates. |
|
|
59
|
+
| [test/e2e/Readme.md](./test/e2e/Readme.md) | The **end-to-end test suite** — its three layers, what each covers, and how to run it. |
|
|
36
60
|
|
|
37
61
|
---
|
|
38
62
|
|
|
39
63
|
## Agentic Workflows
|
|
40
64
|
One of the more powerful features of the **UAG** is the `agent_provide_data` tool. This tool provides the ability to instruct a generically trained agent how to analyze existing submission data, and then produce its own data by following a configurable **Criteria**. This behavior historically could only be achieved using a specifically trained agent, which does not provide any benefits of dynamic configurability that the Form.io platform offers. This tool is able to achieve this goal by providing a generally trained agent with the necessary "context" it needs to accurately produce its own data as part of an automated workflow. This feature is particularly helpful if you wish to utilize the UAG within an Agentic Workflow, where the AI Agent is capable of understanding structured data, and then contribute its own data by following the configured **Criteria** "context" provided by the UAG.
|
|
41
65
|
|
|
42
|
-
For example, let's suppose you wish to automate the backend administration behind a College Application Process. In this example, a potential student submits an application that consists of many different fields of data, such as
|
|
66
|
+
For example, let's suppose you wish to automate the backend administration behind a College Application Process. In this example, a potential student submits an application that consists of many different fields of data, such as Academics, Extra curricular activities, Honors, Volunteer work, as well as possibly written Essays. Historically, these applications would be reviewed by an administrator in order to assess the candidates qualifications for acceptance. With the `agent_provide_data` tool, it is now possible to automate this process as the following diagram illustrates.
|
|
43
67
|
|
|
44
68
|

|
|
45
69
|
|
|
@@ -51,6 +75,24 @@ To achieve this feat, the `agent_provide_data` tool utilizes the following infor
|
|
|
51
75
|
|
|
52
76
|
### Setup
|
|
53
77
|
To configure a form to use the `agent_provide_data` tool, you must first designate a section of your form that will be read and used by the AI Agent. This is similar to what you would see in a form that says "For Office Use Only", but instead of a Human contributing to the values of this section, it will be an automated AI Agent. There are two types of fields that can be added to a form to configure it for use by the `agent_provide_data` tool: **Criteria** and **Agent Fields**
|
|
78
|
+
|
|
79
|
+
A complete setup is four steps, and all four are required:
|
|
80
|
+
|
|
81
|
+
1. **Tag the form `uag`** (described below) — without this the UAG will not register the form at all.
|
|
82
|
+
2. Add the **Criteria** content component that teaches the agent what to do.
|
|
83
|
+
3. Add the **Agent Fields** the agent is allowed to fill.
|
|
84
|
+
4. Add a **Webhook action** to trigger the agent on submission — see the [Claude Integration](./integrations/claude/Readme.md#formio-webhook-setup). This step requires the Enterprise Server, because the action has to set a request header and transform its payload; on Community Edition, trigger the integration from your own code after creating the submission.
|
|
85
|
+
|
|
86
|
+
For a working reference containing all four, see the [Agentic Workflow example](./examples/agentic-workflow).
|
|
87
|
+
|
|
88
|
+
#### Tagging the form `uag`
|
|
89
|
+
The UAG only registers forms that carry the `uag` tag. Any form without it is invisible to every agent and to every tool, including `get_forms`. In the Form Builder this is set under **Form Settings → Tags**; in a form's JSON it looks like this:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
"tags": ["uag"]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This is worth checking first whenever an agent insists that a form does not exist, because a missing tag produces no error anywhere — the form simply is not there as far as the agent is concerned. Note also that newly tagged forms are picked up on the next project cache expiry, so allow up to `PROJECT_TTL` seconds (see [Project Cache and TTL](#project-cache-and-ttl)).
|
|
54
96
|
|
|
55
97
|
#### Agent "Criteria" component
|
|
56
98
|
The first thing that needs to be configured is a special **Content Component** that instructs the AI Agent how to interpret the data. For example, here is a Criteria content block that was written to instruct an AI Agent on how to assess the submission of a College Application Essay.
|
|
@@ -80,10 +122,42 @@ It is also possible to "flag" a collection of fields with the `uag` property. Th
|
|
|
80
122
|
### Multiple Personas per form
|
|
81
123
|
It is also possible to achieve multiple personas per-form. This is very helpful if there are several isolated evaluations that need to occur within a process. For example, if we look at the flow chart diagram shown above, we can see a College Application Form. The first agentic evaluation occurs when the form is submitted by the applicant, where their application is assessed to be accepted or not. From that point, IF the applicant is accepted, a completely different evaluation needs to occur to award Financial Aid or even Scholarships to the applicant. These would require completely different criteria to assess the submission data differently from one another.
|
|
82
124
|
|
|
83
|
-
To achieve this, you simply need to "flag" the components `uag` property value differently with the persona that applies for that field. For the example above, you would add a Content component to assess
|
|
125
|
+
To achieve this, you simply need to "flag" the components `uag` property value differently with the persona that applies for that field. For the example above, you would add a Content component to assess if the applicant should be accepted, and then provide the following `uag` property to that content. `uag="application"`. Any fields that need to be filled out by the AI Agent for application approval would also be flagged with the property `uag="application"`. Then, in a different section of the form, you would create a separate Content component with the criteria for the financial admin AI Agent. This content would be flagged with the property `uag="finance"`. Any fields that the finance admin AI Agent need to fill out would also be flagged with `uag="finance"`. This provides a truly flexible and dynamic AI engagement where multiple generically trained agents can be "taught" dynamically how to read and interpret data that is only relevant to that part of the agentic process.
|
|
126
|
+
|
|
127
|
+
Agent fields do not have to sit next to their criteria, either. A `status` field in one panel can be flagged `uag="application"` while the field beside it is flagged `uag="finance"`, which is how a single workflow field can be owned by whichever persona is responsible for that stage.
|
|
128
|
+
|
|
129
|
+
#### Chaining personas into a workflow
|
|
130
|
+
Multiple personas on a form are only useful if something triggers the later ones. Because each persona writes to the submission, the natural trigger for the second persona is the *first* persona's write.
|
|
131
|
+
|
|
132
|
+
This is configured with a second Webhook action that fires on `update` rather than `create`, narrowed by a condition on a field that the first persona sets:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"name": "webhook", "handler": ["after"], "method": ["update"], "priority": 0,
|
|
137
|
+
"condition": {
|
|
138
|
+
"conjunction": "all",
|
|
139
|
+
"conditions": [
|
|
140
|
+
{ "component": "status", "operator": "isEqual", "value": "scholarshipEvaluation" }
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
"settings": {
|
|
144
|
+
"method": "post",
|
|
145
|
+
"url": "http://formio-uag-claude:3300/agent/claude/agent_provide_data",
|
|
146
|
+
"headers": [{ "header": "x-token", "value": "<your project API key>" }],
|
|
147
|
+
"transform": "payload = {formName: 'application', submissionId: payload.submission._id, persona: 'finance'};",
|
|
148
|
+
"block": false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
In the college application workflow, the admissions persona sets `status` to `scholarshipEvaluation` only for applicants on the scholarship track, so only those submissions wake the finance persona. Applicants who were simply accepted or declined never trigger it.
|
|
154
|
+
|
|
155
|
+
> **Avoid loops.** The second persona also writes to the submission, which fires the same `update` webhook again. Make sure each persona's own write cannot re-match the condition that triggered it — in the example above the finance persona sets `status` to `scholarshipGranted`, which no longer satisfies the condition, so the workflow terminates. A condition that stays true after the persona runs will loop indefinitely and consume API credits until you stop the container.
|
|
156
|
+
|
|
157
|
+
A complete, runnable version of this two-persona workflow is in the [Agentic Workflow example](./examples/agentic-workflow).
|
|
84
158
|
|
|
85
159
|
## Integrations
|
|
86
|
-
In order to
|
|
160
|
+
In order to aid in the "agentification" of the UAG, we have also provided some "integrations" that can be used to directly communicate with the Generically trained AI Agents to utilize the UAG to accomplish automated AI Agent behaviors. This involves providing a single API endpoint that is capable of triggering the AI Agent process along with the connections to your deployed UAG. These API endpoints can then be triggered using a simple Webhook action within a form to start an Agentic process as the result of a Form submission. Here is a diagram of how specific integrations can be used to achieve a single API call to accomplish an agentic task.
|
|
87
161
|
|
|
88
162
|

|
|
89
163
|
|
|
@@ -137,7 +211,7 @@ While the UAG can be used as a stand-alone system to enable the interaction betw
|
|
|
137
211
|
2. **Custom Actions**: Actions can be thought of as composable middleware functions that allow for configurable Express.js middleware functions to be attached to any Form and Resource, and then to be executed once the data being submitted by an AI agent has passed through the proper data validations and sanitization. These can be used to provide custom integrations to backend processes and workflows.
|
|
138
212
|
3. **Custom Tools**: In addition to providing custom actions, a Module can also introduce custom MCP Tools to provide bespoke interaction capabilities between the UAG and the AI agents for any industry specific purposes.
|
|
139
213
|
4. **Custom Authentication**: A module can also modify the "authentication" landing page to assist with any custom requirements that are needed around Authentication.
|
|
140
|
-
5. **Behavior Overrides**: A module also has the ability to "override" any default behavior from the UAG. This ensures that whomever is using a certain module can "tweak" certain tools and responses to achieve better results as it pertains
|
|
214
|
+
5. **Behavior Overrides**: A module also has the ability to "override" any default behavior from the UAG. This ensures that whomever is using a certain module can "tweak" certain tools and responses to achieve better results as it pertains to their industry specific needs.
|
|
141
215
|
|
|
142
216
|
Form.io aims to enable developers to build and contribute their own custom modules to the broader community through GitHub and NPM.
|
|
143
217
|
|
|
@@ -184,6 +258,8 @@ Before deploying, it is worth deciding **which side opens the connection** to th
|
|
|
184
258
|
|
|
185
259
|
With a **local MCP client**, your application connects *out* to the UAG, calls the tools itself, and passes the results on to whichever model it is using. Nothing dials back in, so the UAG can stay entirely inside your network. This is how the [Claude Integration](./integrations/claude) works, and it is the recommended model for server-to-server and webhook-triggered automation — including running the whole stack on a developer machine.
|
|
186
260
|
|
|
261
|
+
> If you are using the published Claude integration image, this requires **`formio/uag-claude:1.3.0` or newer**. Earlier images handed the MCP server URL to the Anthropic API as a *remote* connector, meaning the API dialed into your UAG and a public URL was mandatory. An older image pointed at a private address fails with a `400` from the Anthropic API reading `Connection error while communicating with MCP server`, which looks like a UAG fault but is not one.
|
|
262
|
+
|
|
187
263
|
With a **remote MCP client**, the agent lives outside your network and connects in, so `/mcp` and the `.well-known` OAuth discovery endpoints must be publicly reachable over HTTPS, and `BASE_URL` must be that public URL. See [Domain and URL Configuration](#domain-and-url-configuration).
|
|
188
264
|
|
|
189
265
|
Both models can be used against the same UAG deployment at the same time.
|
|
@@ -210,8 +286,8 @@ The response contains an `access_token`, which is passed to the UAG as `Authoriz
|
|
|
210
286
|
|
|
211
287
|
### Runtime Environments
|
|
212
288
|
There are currently two run-time environments that work with the UAG:
|
|
213
|
-
-[**Docker**](#docker)
|
|
214
|
-
-[**Node.js (Express)**](#nodejs-express)
|
|
289
|
+
- [**Docker**](#docker)
|
|
290
|
+
- [**Node.js (Express)**](#nodejs-express)
|
|
215
291
|
|
|
216
292
|
|
|
217
293
|
### Docker
|
|
@@ -222,16 +298,15 @@ formio/uag
|
|
|
222
298
|
```
|
|
223
299
|
|
|
224
300
|
This container can be run in two ways:
|
|
225
|
-
- [**Docker-Compose.yml**](#docker-compose-recommended) - The recommended method, deploys
|
|
301
|
+
- [**Docker-Compose.yml**](#docker-compose-recommended) - The recommended method, deploys as a multi-container Docker Compose stack.
|
|
226
302
|
- [**Docker Run**](#docker-run) - a standalone container using the common `docker run` command.
|
|
227
303
|
|
|
228
304
|
|
|
229
305
|
#### Docker Compose (Recommended)
|
|
230
|
-
The recommended way to
|
|
306
|
+
The recommended way to launch the UAG is through Docker Compose. This enables you to orchestrate several of the containers to run within a single instance to provide a more seamless and simple way of managing your deployments. Here is a simple example of how to run both the Form.io UAG + Form.io OSS server on the same instance.
|
|
231
307
|
|
|
232
308
|
docker-compose.yml
|
|
233
309
|
```
|
|
234
|
-
version: "3.8"
|
|
235
310
|
services:
|
|
236
311
|
mongo:
|
|
237
312
|
image: mongo
|
|
@@ -271,8 +346,10 @@ services:
|
|
|
271
346
|
ADMIN_KEY: CHANGEME
|
|
272
347
|
JWT_SECRET: CHANGEME
|
|
273
348
|
JWT_EXPIRE_TIME: 525600
|
|
274
|
-
LOGIN_FORM: http://localhost:3000/
|
|
275
|
-
|
|
349
|
+
LOGIN_FORM: http://localhost:3000/user/login
|
|
350
|
+
# The address clients reach the UAG at — this is the UAG's own port,
|
|
351
|
+
# not the Form.io server's. See the BASE_URL section below.
|
|
352
|
+
BASE_URL: http://localhost:3200
|
|
276
353
|
ports:
|
|
277
354
|
- "3200:3200"
|
|
278
355
|
```
|
|
@@ -302,7 +379,7 @@ docker run -d \
|
|
|
302
379
|
formio/uag
|
|
303
380
|
```
|
|
304
381
|
|
|
305
|
-
**UAG pointed to
|
|
382
|
+
**UAG pointed to a Form.io Enterprise Server**
|
|
306
383
|
```
|
|
307
384
|
docker run -d \
|
|
308
385
|
-e "PROJECT=https://forms.mysite.com/myproject" \
|
|
@@ -324,7 +401,7 @@ Once it is running, you can then navigate to the following to access both the OS
|
|
|
324
401
|
- http://localhost:3000: The Form.io OSS Server
|
|
325
402
|
- http://localhost:3200: The UAG Server
|
|
326
403
|
|
|
327
|
-
In both the Node.js runtime
|
|
404
|
+
In both the Node.js runtime environment as well as Docker, the way to control the UAG is through the use of **Environment Variables** and **Modules**.
|
|
328
405
|
|
|
329
406
|
### Environment Variables
|
|
330
407
|
This module can be configured in many ways. One of those ways is through the use of Environment Variables, which are documented as follows.
|
|
@@ -332,7 +409,7 @@ This module can be configured in many ways. One of those ways is through the use
|
|
|
332
409
|
| Variable | Description | Example |
|
|
333
410
|
|----------|-------------|---------|
|
|
334
411
|
| PROJECT | The API Endpoint to either an Enterprise project endpoint, or the OSS server url. | http://localhost:3000 |
|
|
335
|
-
| PROJECT_KEY | (Enterprise Only)
|
|
412
|
+
| PROJECT_KEY | (Enterprise Only) The API Key of the project this UAG is bound to, found in **Project Settings → API Keys**. Open Source deployments use `ADMIN_KEY` instead. | CHANGEME |
|
|
336
413
|
| ADMIN_KEY | (Open Source Only) Allows you to provide the ADMIN_KEY to install and connect to the OSS Server. | CHANGEME |
|
|
337
414
|
| UAG_LICENSE | The license to run the UAG against a Form.io Enterprise Deployment. | |
|
|
338
415
|
| PROJECT_TTL | The project cache TTL in seconds. This controls the amount of time to check for any "changes" that have been made to the project to refresh any forms and resources within the UAG. | 900 |
|
|
@@ -346,9 +423,10 @@ This module can be configured in many ways. One of those ways is through the use
|
|
|
346
423
|
| BASE_URL | **Required.** The URL that the UAG is reachable at *by its clients*. It is published in the `.well-known` OIDC (PKCE) definitions and used for authentication callbacks. This must be a public URL when remote agents connect in; for a local MCP client, a private or localhost URL is fine. | https://forms.yoursite.com |
|
|
347
424
|
| LOGIN_FORM | The URL to the Login Form JSON endpoint, loaded by the `/auth/authorize` page. Only used by the browser-based login flow, so it must be reachable by that browser. | https://mysite.com/project/user/login |
|
|
348
425
|
| CORS | The cors domain, or the JSON configuration to configure the "cors" node.js module cross domain resource sharing. | *.* |
|
|
426
|
+
| NODE_TLS_REJECT_UNAUTHORIZED | Standard Node.js variable. Set to `0` when `PROJECT` is an `https` URL secured by a self-signed or otherwise untrusted certificate, which is common for local and internal deployments — without it the UAG cannot connect to the server and every tool call fails. Never set this on a deployment reachable from outside your network, since it disables certificate verification for all outbound requests. | 0 |
|
|
349
427
|
|
|
350
428
|
### Project Cache and TTL
|
|
351
|
-
By default, the UAG uses a TTL to "fetch" any of the project assets and register them and cache them into the UAG. What this means is that if you make any changes to your underlying project forms and resources (either directly or through a deployment), you need to wait a maximum of the TTL setting (in seconds) to see any updates that have been made. For example, if the TTL is set to 900 (or 15 minutes), and you make a change to any forms and resources, you must wait at most 15 minutes to see any of these changes take effect into the UAG. It should be noted that when a "refresh" occurs, an API call to the "export" method is called on the underlying project endpoint. This is a processor intensive API call, so it is not recommended to set the TTL to any value less than 60. A value of 0 is interpreted as "No TTL", which means the only way to refresh the project forms and
|
|
429
|
+
By default, the UAG uses a TTL to "fetch" any of the project assets and register them and cache them into the UAG. What this means is that if you make any changes to your underlying project forms and resources (either directly or through a deployment), you need to wait a maximum of the TTL setting (in seconds) to see any updates that have been made. For example, if the TTL is set to 900 (or 15 minutes), and you make a change to any forms and resources, you must wait at most 15 minutes to see any of these changes take effect into the UAG. It should be noted that when a "refresh" occurs, an API call to the "export" method is called on the underlying project endpoint. This is a processor intensive API call, so it is not recommended to set the TTL to any value less than 60. A value of 0 is interpreted as "No TTL", which means the only way to refresh the project forms and resources, a UAG reboot is necessary.
|
|
352
430
|
|
|
353
431
|
Here is a table explaining how this parameter can be used.
|
|
354
432
|
|
|
@@ -358,6 +436,12 @@ Here is a table explaining how this parameter can be used.
|
|
|
358
436
|
| PROJECT_TTL | 60 | Check for changes every minute |
|
|
359
437
|
| PROJECT_TTL | 3600 | Check for changes every hour |
|
|
360
438
|
|
|
439
|
+
This cache is the reason a form edit does not appear to take effect immediately. Anything that changes what the agent knows — adding the `uag` tag, rewriting a **Criteria** content component, adding or removing **Agent Fields** — only reaches the agent after the next expiry. When you are actively iterating on criteria, restarting the UAG re-registers the project immediately instead of waiting:
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
docker restart formio-uag
|
|
443
|
+
```
|
|
444
|
+
|
|
361
445
|
### Domain and URL Configuration
|
|
362
446
|
There are 3 different "domain" environment variables that matter, and it is important to understand how to configure them depending on your use case. Whether any of them need to be *public* depends on how agents connect — see [How Agents Connect](#how-agents-connect).
|
|
363
447
|
|
|
@@ -371,7 +455,6 @@ If you are using a local connection, such as within a Docker Compose file, you c
|
|
|
371
455
|
|
|
372
456
|
docker-compose.yml
|
|
373
457
|
```
|
|
374
|
-
version: "3.8"
|
|
375
458
|
services:
|
|
376
459
|
formio:
|
|
377
460
|
image: formio/formio:rc
|
|
@@ -398,7 +481,6 @@ If you are using the Enterprise Form.io server within a local environment to the
|
|
|
398
481
|
|
|
399
482
|
docker-compose.yml
|
|
400
483
|
```
|
|
401
|
-
version: "3.8"
|
|
402
484
|
services:
|
|
403
485
|
formio-enterprise:
|
|
404
486
|
image: formio/formio-enterprise
|
|
@@ -422,7 +504,6 @@ If your project does not reside on the same network as the UAG, you can provide
|
|
|
422
504
|
|
|
423
505
|
docker-compose.yml: Connected to Enterprise (formio/formio-enterprise)
|
|
424
506
|
```
|
|
425
|
-
version: "3.8"
|
|
426
507
|
services:
|
|
427
508
|
formio-uag:
|
|
428
509
|
image: formio/uag
|
|
@@ -434,7 +515,6 @@ services:
|
|
|
434
515
|
|
|
435
516
|
docker-compose.yml: Connected to Open Source (formio/formio)
|
|
436
517
|
```
|
|
437
|
-
version: "3.8"
|
|
438
518
|
services:
|
|
439
519
|
formio-uag:
|
|
440
520
|
image: formio/uag
|
|
@@ -517,3 +597,23 @@ In order to run the UAG against an Enterprise Server, you need to provide a few
|
|
|
517
597
|
|
|
518
598
|
Once you have these environment variables in place, you should be able to run the UAG pointed to your Enterprise Project.
|
|
519
599
|
|
|
600
|
+
## Troubleshooting
|
|
601
|
+
Most UAG problems are configuration, not code, and they tend to surface far from their cause — a missing tag looks like a missing form, and a missing `BASE_URL` looks like a permissions error. The table below maps the symptoms you are likely to see back to what actually produces them.
|
|
602
|
+
|
|
603
|
+
| Symptom | Likely cause |
|
|
604
|
+
|---------|--------------|
|
|
605
|
+
| The agent says a form does not exist, or `get_forms` does not list it | The form is not tagged `uag`, or the change has not survived the project cache yet. See [Tagging the form `uag`](#tagging-the-form-uag) and [Project Cache and TTL](#project-cache-and-ttl). |
|
|
606
|
+
| `/auth/token` returns `404`, and `/.well-known/*` is also missing | `BASE_URL` is not set. The authentication provider is only enabled when it is present, so those endpoints do not exist at all. See [BASE_URL](#base_url). |
|
|
607
|
+
| Every `/mcp` request returns `401` | Either `BASE_URL` is unset (as above), or the client is not sending `Authorization: Bearer <token>`. For a server-side client, see [Authenticating a local MCP client](#authenticating-a-local-mcp-client). |
|
|
608
|
+
| A remote agent (claude.ai, Claude Desktop connector) cannot authenticate | `BASE_URL` does not point at a publicly reachable HTTPS URL. The agent reads that value out of the discovery document and follows it, so it must be correct from the agent's perspective, not the server's. |
|
|
609
|
+
| The Claude integration returns `400 ... Connection error while communicating with MCP server` | The `formio/uag-claude` image predates `1.3.0` and is treating the UAG as a *remote* MCP server, so the Anthropic API is trying to dial into a private address. Upgrade to `1.3.0` or newer. See [How Agents Connect](#how-agents-connect). |
|
|
610
|
+
| The UAG cannot reach the server at all, and every tool fails | If `PROJECT` is an `https` URL with a self-signed certificate, set `NODE_TLS_REJECT_UNAUTHORIZED=0`. Otherwise check that `PROJECT` is resolvable *from inside the UAG container* — it is a container-to-container address, not a browser one. See [PROJECT](#project). |
|
|
611
|
+
| Nothing happens when a submission is created | The Webhook action did not fire. Confirm its `handler` is `after` and `method` includes `create`, and that the API key header matches the integration's `PROJECT_KEY` (Enterprise) or `ADMIN_KEY` (OSS). |
|
|
612
|
+
| A submission is created but the response hangs for a long time | The Webhook action has `block: true`. Agent runs take tens of seconds; set `block: false` so the submitter is not made to wait. |
|
|
613
|
+
| An agent runs continuously and burns API credits | A chained persona is re-triggering itself. Its own write still satisfies the condition on the `update` webhook that started it. See [Chaining personas into a workflow](#chaining-personas-into-a-workflow). |
|
|
614
|
+
| The agent fills in some fields but not others | Those fields are missing the `uag` property, or its value does not exactly match the persona on the **Criteria** component. Every field the agent may write must carry the same persona, either directly or by inheriting it from a parent component. |
|
|
615
|
+
| The agent writes values the form rejects | The **Criteria** is not specific enough about formats and allowed values. State units, rounding, and exact option values, and name the field keys verbatim. |
|
|
616
|
+
| A run fails and the logs are not enough to see where | Put the [Flow Viewer](./examples/flow-viewer) in front of the UAG. It shows the token request, `tools/list`, each tool call with arguments, and the write-back in order. |
|
|
617
|
+
|
|
618
|
+
If none of these apply, run one of the [examples](#try-it-out) to confirm the environment itself is sound, then compare its configuration against yours.
|
|
619
|
+
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fetchExternalData = void 0;
|
|
4
4
|
const template_1 = require("../template");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
5
6
|
const core_1 = require("@formio/core");
|
|
6
7
|
const SchemaBuilder_1 = require("./SchemaBuilder");
|
|
7
8
|
const lodash_1 = require("lodash");
|
|
@@ -251,7 +252,7 @@ async function fetchResourceData(component, search_value, authInfo, project, con
|
|
|
251
252
|
const query = {};
|
|
252
253
|
// Add search filter if provided
|
|
253
254
|
if (search_value && component.searchField) {
|
|
254
|
-
query[`data.${component.searchField}__regex`] = `/${search_value}/i`;
|
|
255
|
+
query[`data.${component.searchField}__regex`] = `/${(0, utils_1.escapeSearchPattern)(search_value)}/i`;
|
|
255
256
|
}
|
|
256
257
|
// Add component filter if configured (interpolate the filter string)
|
|
257
258
|
if (component.filter) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.findSubmission = void 0;
|
|
4
4
|
const template_1 = require("../template");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
5
6
|
const lodash_1 = require("lodash");
|
|
6
7
|
const SchemaBuilder_1 = require("./SchemaBuilder");
|
|
7
8
|
const error = require('debug')('formio:uag:findSubmission:error');
|
|
@@ -34,19 +35,33 @@ const findSubmission = async (project) => {
|
|
|
34
35
|
}, true);
|
|
35
36
|
}
|
|
36
37
|
if (criterion.operator === 'regex') {
|
|
38
|
+
// The value is a pattern by request here, so it is passed through
|
|
39
|
+
// unescaped. It is still compiled first: the server drops a filter
|
|
40
|
+
// it cannot compile, which would widen the search instead of
|
|
41
|
+
// failing it, so a bad pattern is better reported back.
|
|
42
|
+
try {
|
|
43
|
+
new RegExp(criterion.search_value);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return project.mcpResponse(template_1.ResponseTemplate.submissionSearchError, {
|
|
47
|
+
form: form.form,
|
|
48
|
+
searchQuery: search_query,
|
|
49
|
+
error: `"${criterion.search_value}" is not a valid regular expression for "${criterion.data_path}". Use the "contains" operator to search for it literally.`
|
|
50
|
+
}, true);
|
|
51
|
+
}
|
|
37
52
|
query[`data.${criterion.data_path}__regex`] = `/${criterion.search_value}/i`;
|
|
38
53
|
}
|
|
39
54
|
else if (criterion.operator === 'equals') {
|
|
40
55
|
query[`data.${criterion.data_path}`] = criterion.search_value;
|
|
41
56
|
}
|
|
42
57
|
else if (criterion.operator === 'contains') {
|
|
43
|
-
query[`data.${criterion.data_path}__regex`] = `/${criterion.search_value}/i`;
|
|
58
|
+
query[`data.${criterion.data_path}__regex`] = `/${(0, utils_1.escapeSearchPattern)(criterion.search_value)}/i`;
|
|
44
59
|
}
|
|
45
60
|
else if (criterion.operator === 'starts_with') {
|
|
46
|
-
query[`data.${criterion.data_path}__regex`] = `/^${criterion.search_value}/i`;
|
|
61
|
+
query[`data.${criterion.data_path}__regex`] = `/^${(0, utils_1.escapeSearchPattern)(criterion.search_value)}/i`;
|
|
47
62
|
}
|
|
48
63
|
else if (criterion.operator === 'ends_with') {
|
|
49
|
-
query[`data.${criterion.data_path}__regex`] = `/${criterion.search_value}$/i`;
|
|
64
|
+
query[`data.${criterion.data_path}__regex`] = `/${(0, utils_1.escapeSearchPattern)(criterion.search_value)}$/i`;
|
|
50
65
|
}
|
|
51
66
|
else if (criterion.operator === 'greater_than') {
|
|
52
67
|
query[`data.${criterion.data_path}__gt`] = criterion.search_value;
|
package/lib/tools/utils.d.ts
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { ZodRawShape } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Escape a literal value so it can be carried in a `__regex` query parameter.
|
|
4
|
+
*
|
|
5
|
+
* Operators like "contains" promise a literal match, so every regular expression
|
|
6
|
+
* metacharacter in the value has to be neutered first. Left raw, a search for
|
|
7
|
+
* "joe.thompson@example.com" becomes a pattern whose dots match any character,
|
|
8
|
+
* and a value such as "[" is not a valid pattern at all — the server compiles
|
|
9
|
+
* these with a try/catch and silently drops the filter when compilation fails,
|
|
10
|
+
* which turns a narrow search into one that matches everything.
|
|
11
|
+
*
|
|
12
|
+
* Forward slashes get a further step. The server reads the parameter as
|
|
13
|
+
* `/pattern/flags` and takes the pattern with `[^/]+`, so an embedded slash
|
|
14
|
+
* truncates the pattern and the remainder is parsed as flags — again dropping
|
|
15
|
+
* the filter. Writing it as the equivalent `\x2f` escape keeps the character
|
|
16
|
+
* out of the transport while still matching a literal slash.
|
|
17
|
+
*
|
|
18
|
+
* @param value - The literal text to search for.
|
|
19
|
+
* @returns The value as a regular expression pattern that matches it literally.
|
|
20
|
+
*/
|
|
21
|
+
export declare const escapeSearchPattern: (value: string) => string;
|
|
2
22
|
export type ToolInfo = {
|
|
3
23
|
name?: string;
|
|
4
24
|
title?: string;
|
package/lib/tools/utils.js
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.escapeSearchPattern = void 0;
|
|
4
|
+
const lodash_1 = require("lodash");
|
|
5
|
+
/**
|
|
6
|
+
* Escape a literal value so it can be carried in a `__regex` query parameter.
|
|
7
|
+
*
|
|
8
|
+
* Operators like "contains" promise a literal match, so every regular expression
|
|
9
|
+
* metacharacter in the value has to be neutered first. Left raw, a search for
|
|
10
|
+
* "joe.thompson@example.com" becomes a pattern whose dots match any character,
|
|
11
|
+
* and a value such as "[" is not a valid pattern at all — the server compiles
|
|
12
|
+
* these with a try/catch and silently drops the filter when compilation fails,
|
|
13
|
+
* which turns a narrow search into one that matches everything.
|
|
14
|
+
*
|
|
15
|
+
* Forward slashes get a further step. The server reads the parameter as
|
|
16
|
+
* `/pattern/flags` and takes the pattern with `[^/]+`, so an embedded slash
|
|
17
|
+
* truncates the pattern and the remainder is parsed as flags — again dropping
|
|
18
|
+
* the filter. Writing it as the equivalent `\x2f` escape keeps the character
|
|
19
|
+
* out of the transport while still matching a literal slash.
|
|
20
|
+
*
|
|
21
|
+
* @param value - The literal text to search for.
|
|
22
|
+
* @returns The value as a regular expression pattern that matches it literally.
|
|
23
|
+
*/
|
|
24
|
+
const escapeSearchPattern = (value) => (0, lodash_1.escapeRegExp)(value).replace(/\//g, '\\x2f');
|
|
25
|
+
exports.escapeSearchPattern = escapeSearchPattern;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formio/uag",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.2",
|
|
4
4
|
"mcpName": "io.form/uag",
|
|
5
5
|
"description": "The Form.io Universal Agent Gateway (UAG).",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"author": "Form.io Developers",
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@formio/appserver": "^2.13.
|
|
33
|
+
"@formio/appserver": "^2.13.2",
|
|
34
34
|
"@formio/core": "2.8.0",
|
|
35
35
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
36
36
|
"cors": "^2.8.6",
|