@formio/uag 1.9.1 → 1.10.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 +1 -0
- package/lib/UAGFormInterface.d.ts +4 -0
- package/lib/UAGFormInterface.js +57 -14
- package/lib/UAGProjectInterface.d.ts +3 -3
- package/lib/UAGProjectInterface.js +10 -9
- package/lib/config.d.ts +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +7 -0
- package/lib/router.js +6 -2
- package/lib/template.d.ts +4 -2
- package/lib/template.js +3 -1
- package/lib/templates/collectedData.md +2 -2
- package/lib/templates/fetchExternalData.md +10 -0
- package/lib/templates/fetchExternalDataError.md +6 -0
- package/lib/templates/fieldValues.md +1 -1
- package/lib/templates/formData.md +5 -0
- package/lib/templates/submitValidationError.md +1 -1
- package/lib/tools/SchemaBuilder.d.ts +13 -5
- package/lib/tools/SchemaBuilder.js +40 -12
- package/lib/tools/fetchExternalData.d.ts +3 -0
- package/lib/tools/fetchExternalData.js +268 -0
- package/lib/tools/index.d.ts +2 -1
- package/lib/tools/index.js +5 -2
- package/lib/tools/submitForm.js +3 -11
- package/package.json +8 -8
- package/lib/templates/submittedData.md +0 -4
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ The following tools provided by the UAG can be described as follows:
|
|
|
88
88
|
| 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. |
|
|
89
89
|
| 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. |
|
|
90
90
|
| agent_provide_data | Enables an AI Agent to be able to analyze existing submission data according to rules defined with a configurable **Criteria**. It will then instruct the Agent to provide generated data for fields configured for a specific **persona** using the `submission_update` tool. Please read the [**Agentic Workflows**](#agentic-workflows) section for more information about this toolset. |
|
|
91
|
+
| fetch_external_data | This tool enables an AI Agent to pull external data into a form workflow through the use of either a Resource component, Select with URL, or a Data Source component. |
|
|
91
92
|
|
|
92
93
|
## Agentic Workflows
|
|
93
94
|
One of the more powerful features of the **UAG** is the `agent_process_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.
|
|
@@ -91,6 +91,10 @@ export declare class UAGFormInterface extends FormInterface {
|
|
|
91
91
|
} | {
|
|
92
92
|
data?: undefined;
|
|
93
93
|
} | null;
|
|
94
|
+
/**
|
|
95
|
+
* Override the process methods for the UAG.
|
|
96
|
+
*/
|
|
97
|
+
process(submission: Submission, auth: AuthRequest, headers?: any, findQuery?: any, additional?: any[]): Promise<any>;
|
|
94
98
|
/**
|
|
95
99
|
* Get the relevant fields from the current form. This will return any non-nested input components whose
|
|
96
100
|
* 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
|
@@ -74,10 +74,10 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
74
74
|
}
|
|
75
75
|
if (component.type === 'select' || component.type === 'selectboxes') {
|
|
76
76
|
if (component.dataSrc === 'url') {
|
|
77
|
-
fieldInfo.options = [{ label: '**
|
|
77
|
+
fieldInfo.options = [{ label: '** USE fetch_external_data TOOL **', value: `Call the \`fetch_external_data\` tool with \`field_path\`="${path}" to retrieve available options from the external URL.` }];
|
|
78
78
|
}
|
|
79
79
|
else if (component.dataSrc === 'resource') {
|
|
80
|
-
fieldInfo.options = [{ label: '**
|
|
80
|
+
fieldInfo.options = [{ label: '** USE fetch_external_data TOOL **', value: `Call the \`fetch_external_data\` tool with \`field_path\`="${path}" to retrieve available options from the Form.io resource (${component.data.resource}).` }];
|
|
81
81
|
}
|
|
82
82
|
else if (component.dataSrc === 'json') {
|
|
83
83
|
fieldInfo.options = [{ label: '** ANY VALUE IS ALLOWED **', value: 'Options are not dynamically defined.' }];
|
|
@@ -95,6 +95,9 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
if (component.type === 'datasource') {
|
|
99
|
+
fieldInfo.options = [{ label: '** USE fetch_external_data TOOL **', value: `Call the \`fetch_external_data\` tool with \`field_path\`="${path}" to retrieve data from the external datasource.` }];
|
|
100
|
+
}
|
|
98
101
|
fieldInfo.nested = this.isNestedComponent(component);
|
|
99
102
|
return fieldInfo;
|
|
100
103
|
}
|
|
@@ -203,6 +206,13 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
203
206
|
break;
|
|
204
207
|
case 'selectboxes':
|
|
205
208
|
case 'select':
|
|
209
|
+
if (component.dataSrc === 'url' || component.dataSrc === 'resource') {
|
|
210
|
+
rule += `You MUST first call the \`fetch_external_data\` tool with the \`field_path\` set to this component's \`data_path\` to retrieve the available options, then select ${this.isMultiple(component) ? 'one or more (as comma separated values)' : 'one'} of the returned options. Use the **value** (not the label) when setting this field's data.`;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
rule += `The value must be ${this.isMultiple(component) ? 'one or more (as comma separated values)' : 'one'} of the following options provided in the "**Options**" section of that component, formatted as " - Label (value)":`;
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
206
216
|
case 'radio':
|
|
207
217
|
rule += `The value must be ${this.isMultiple(component) ? 'one or more (as comma separated values)' : 'one'} of the following options provided in the "**Options**" section of that component, formatted as " - Label (value)":`;
|
|
208
218
|
break;
|
|
@@ -221,6 +231,20 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
221
231
|
case 'email':
|
|
222
232
|
rule += 'The value must be a valid email address.';
|
|
223
233
|
break;
|
|
234
|
+
case 'datasource':
|
|
235
|
+
rule += 'You MUST first call the `fetch_external_data` tool with the `field_path` set to this component\'s `data_path` to retrieve the external data. The value will be set automatically from the fetched data.';
|
|
236
|
+
break;
|
|
237
|
+
case 'survey':
|
|
238
|
+
rule += 'The value is an object of question/value pairs based on the following survey configuration.\n';
|
|
239
|
+
rule += ' **Keys**: The value `keys` will be the "value" property for each question in the survey configuration.';
|
|
240
|
+
component.questions.forEach((question, index) => {
|
|
241
|
+
rule += `\n - **${question.label}** (${question.value}): ${question.tooltip || ''}`;
|
|
242
|
+
});
|
|
243
|
+
rule += '\n\n **Values**: Only ONE of the the following values are allowed for each question.';
|
|
244
|
+
component.values.forEach((value) => {
|
|
245
|
+
rule += `\n - **${value.label}** (${value.value})`;
|
|
246
|
+
});
|
|
247
|
+
break;
|
|
224
248
|
default:
|
|
225
249
|
rule += '';
|
|
226
250
|
}
|
|
@@ -276,6 +300,22 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
276
300
|
return null;
|
|
277
301
|
}
|
|
278
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Override the process methods for the UAG.
|
|
305
|
+
*/
|
|
306
|
+
async process(submission, auth, headers = {}, findQuery = {}, additional = []) {
|
|
307
|
+
const processors = [...core_1.Processors, ...additional];
|
|
308
|
+
// Remove the 'fetch' processor since UAG handles this with the fetch_external_data tool.
|
|
309
|
+
const fetchIndex = processors.findIndex((p) => p.name === 'fetch');
|
|
310
|
+
if (fetchIndex !== -1) {
|
|
311
|
+
processors.splice(fetchIndex, 1);
|
|
312
|
+
}
|
|
313
|
+
// Return the processed submission data.
|
|
314
|
+
const context = await super.process(submission, auth, headers, findQuery, processors);
|
|
315
|
+
// Filter the "required" validation since the UAG retrieves required fields separately.
|
|
316
|
+
context.scope.errors = context.scope.errors.filter((error) => (error.ruleName !== 'required'));
|
|
317
|
+
return context;
|
|
318
|
+
}
|
|
279
319
|
/**
|
|
280
320
|
* Get the relevant fields from the current form. This will return any non-nested input components whose
|
|
281
321
|
* values have not already been set within the data model. This allows the agent to know what fields still need to be
|
|
@@ -302,8 +342,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
302
342
|
optional: { components: [], rules: {} }
|
|
303
343
|
};
|
|
304
344
|
const nestedPaths = [];
|
|
305
|
-
const context = await this.process(submission, authInfo, null, null,
|
|
306
|
-
...core_1.Processors,
|
|
345
|
+
const context = await this.process(submission, authInfo, null, null, [
|
|
307
346
|
{
|
|
308
347
|
name: 'getFields',
|
|
309
348
|
shouldProcess: () => true,
|
|
@@ -379,11 +418,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
379
418
|
}
|
|
380
419
|
]);
|
|
381
420
|
if (context.scope?.errors?.length) {
|
|
382
|
-
|
|
383
|
-
context.scope.errors = context.scope.errors.filter((error) => (error.ruleName !== 'required'));
|
|
384
|
-
if (context.scope.errors?.length) {
|
|
385
|
-
fieldInfo.errors = this.convertToFormFieldErrors((0, core_1.interpolateErrors)(context.scope.errors));
|
|
386
|
-
}
|
|
421
|
+
fieldInfo.errors = this.convertToFormFieldErrors((0, core_1.interpolateErrors)(context.scope.errors));
|
|
387
422
|
}
|
|
388
423
|
return fieldInfo;
|
|
389
424
|
}
|
|
@@ -455,16 +490,24 @@ class UAGFormInterface extends appserver_1.FormInterface {
|
|
|
455
490
|
prefix += ' ';
|
|
456
491
|
}
|
|
457
492
|
else if (this.inputComponent(component) && (0, core_1.componentHasValue)(component, value)) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
493
|
+
const modelType = core_1.Utils.getModelType(component);
|
|
494
|
+
if ((0, lodash_1.isObjectLike)(value)) {
|
|
495
|
+
value = JSON.stringify(value);
|
|
496
|
+
}
|
|
497
|
+
else if (modelType === 'string' && (0, lodash_1.isString)(value)) {
|
|
498
|
+
const editor = component.editor;
|
|
499
|
+
if (editor === 'ckeditor' || editor === 'quill') {
|
|
500
|
+
value = this.htmlToMarkdown(value);
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
value = `"${value.trim()}"`;
|
|
504
|
+
}
|
|
462
505
|
}
|
|
463
506
|
uagData.push({
|
|
464
507
|
prefix,
|
|
465
508
|
path,
|
|
466
509
|
label: component.label || component.key || path,
|
|
467
|
-
value
|
|
510
|
+
value
|
|
468
511
|
});
|
|
469
512
|
}
|
|
470
513
|
}, false, false, undefined, undefined, undefined, (component, data) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ProjectInterface, SubmissionRequest } from "@formio/appserver";
|
|
2
2
|
import { UAGConfig } from "./config";
|
|
3
3
|
import { Form } from "@formio/core";
|
|
4
|
-
import { NextFunction, Response } from "express";
|
|
4
|
+
import { NextFunction, Response, Router } from "express";
|
|
5
5
|
import { UAGFormInterface } from "./UAGFormInterface";
|
|
6
6
|
import { ResponseTemplate, UAGTemplate } from "./template";
|
|
7
7
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
@@ -13,8 +13,8 @@ export declare class UAGProjectInterface extends ProjectInterface {
|
|
|
13
13
|
get config(): UAGConfig;
|
|
14
14
|
constructor(endpoint?: string);
|
|
15
15
|
initialize(): Promise<void>;
|
|
16
|
-
addForm(form: Form,
|
|
17
|
-
|
|
16
|
+
addForm(form: Form, machineName: string): UAGFormInterface | undefined;
|
|
17
|
+
uagRouter(): Router;
|
|
18
18
|
authorizeRequest(req: SubmissionRequest, res: Response, next: NextFunction): Promise<Response<any, Record<string, any>> | undefined>;
|
|
19
19
|
mcpJSONResponse(data?: object, isError?: boolean): {
|
|
20
20
|
isError?: boolean | undefined;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UAGProjectInterface = void 0;
|
|
4
4
|
const appserver_1 = require("@formio/appserver");
|
|
5
|
+
const express_1 = require("express");
|
|
5
6
|
const tools_1 = require("./tools");
|
|
6
7
|
const router_1 = require("./router");
|
|
7
8
|
const template_1 = require("./template");
|
|
@@ -36,25 +37,25 @@ class UAGProjectInterface extends appserver_1.ProjectInterface {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
|
-
addForm(form,
|
|
40
|
-
const formInterface = super.addForm(form,
|
|
40
|
+
addForm(form, machineName) {
|
|
41
|
+
const formInterface = super.addForm(form, machineName);
|
|
41
42
|
if (!formInterface)
|
|
42
43
|
return;
|
|
43
|
-
|
|
44
|
+
machineName = formInterface?.form?.machineName;
|
|
44
45
|
if (form.tags?.includes('uag')) {
|
|
45
|
-
debug(`Registering UAG form: ${
|
|
46
|
+
debug(`Registering UAG form: ${machineName}`);
|
|
46
47
|
formInterface.uag = {
|
|
47
|
-
machineName:
|
|
48
|
-
name: form.name || form.path ||
|
|
49
|
-
title: form.title || form.name ||
|
|
48
|
+
machineName: machineName,
|
|
49
|
+
name: form.name || form.path || machineName,
|
|
50
|
+
title: form.title || form.name || machineName,
|
|
50
51
|
description: form.properties?.description || `A form to submit new ${form.title} records.`
|
|
51
52
|
};
|
|
52
53
|
this.formNames.push(formInterface.uag.name);
|
|
53
54
|
}
|
|
54
55
|
return formInterface;
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
-
const router =
|
|
57
|
+
uagRouter() {
|
|
58
|
+
const router = (0, express_1.Router)();
|
|
58
59
|
const uagRouter = (0, router_1.UAGRouter)(this);
|
|
59
60
|
router.use('/mcp', (req, res, next) => this.authorizeRequest(req, res, next), uagRouter);
|
|
60
61
|
return router;
|
package/lib/config.d.ts
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type UAGModule = ServerModule & {
|
|
|
7
7
|
};
|
|
8
8
|
export declare class UAGServer extends Server {
|
|
9
9
|
constructor(config?: UAGConfig);
|
|
10
|
+
router(): Promise<import("express-serve-static-core").Router>;
|
|
10
11
|
}
|
|
11
12
|
export * from '@formio/appserver';
|
|
12
13
|
export * from './config';
|
package/lib/index.js
CHANGED
|
@@ -27,6 +27,7 @@ class UAGServer extends appserver_1.Server {
|
|
|
27
27
|
baseUrl: (0, lodash_1.get)(process.env, 'BASE_URL', '').toString(),
|
|
28
28
|
license: (0, lodash_1.get)(process.env, 'UAG_LICENSE', '').toString(),
|
|
29
29
|
submissionProxy: true,
|
|
30
|
+
processAsClient: true,
|
|
30
31
|
auth: { pkce: true }
|
|
31
32
|
}));
|
|
32
33
|
this.use({
|
|
@@ -34,6 +35,12 @@ class UAGServer extends appserver_1.Server {
|
|
|
34
35
|
FormInterface: UAGFormInterface_1.UAGFormInterface
|
|
35
36
|
});
|
|
36
37
|
}
|
|
38
|
+
async router() {
|
|
39
|
+
const router = await super.router();
|
|
40
|
+
const uagProject = Object.values(this.projectRouter?.projects || {})[0];
|
|
41
|
+
router.use(uagProject.uagRouter());
|
|
42
|
+
return router;
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
exports.UAGServer = UAGServer;
|
|
39
46
|
__exportStar(require("@formio/appserver"), exports);
|
package/lib/router.js
CHANGED
|
@@ -39,7 +39,9 @@ function UAGRouter(project) {
|
|
|
39
39
|
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
40
40
|
sessionIdGenerator: undefined, // Use "undefined" to trigger stateless mode.
|
|
41
41
|
});
|
|
42
|
-
|
|
42
|
+
// Close any existing connection before connecting to a new transport (required in SDK 1.26.0+)
|
|
43
|
+
await project.mcpServer.close();
|
|
44
|
+
await project.mcpServer.connect(transport);
|
|
43
45
|
if (!transport) {
|
|
44
46
|
res.status(500).json(JSON.parse(createJsonRpcErrorResponse(-32603, "Unable to create MCP transport.")));
|
|
45
47
|
return;
|
|
@@ -57,7 +59,9 @@ function UAGRouter(project) {
|
|
|
57
59
|
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
58
60
|
sessionIdGenerator: undefined, // Use "undefined" to trigger stateless mode.
|
|
59
61
|
});
|
|
60
|
-
|
|
62
|
+
// Close any existing connection before connecting to a new transport (required in SDK 1.26.0+)
|
|
63
|
+
await project.mcpServer.close();
|
|
64
|
+
await project.mcpServer.connect(transport);
|
|
61
65
|
if (!transport) {
|
|
62
66
|
res.status(500).json(JSON.parse(createJsonRpcErrorResponse(-32603, "Unable to create MCP transport.")));
|
|
63
67
|
return;
|
package/lib/template.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { UAGTemplateConfig } from './config';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare enum ResponseTemplate {
|
|
7
7
|
collectedData = "collectedData",
|
|
8
|
-
submittedData = "submittedData",
|
|
9
8
|
fieldRules = "fieldRules",
|
|
10
9
|
allFieldsCollected = "allFieldsCollected",
|
|
11
10
|
getFormFieldsEmpty = "getFormFieldsEmpty",
|
|
@@ -32,10 +31,13 @@ export declare enum ResponseTemplate {
|
|
|
32
31
|
fields = "fields",
|
|
33
32
|
fieldList = "fieldList",
|
|
34
33
|
fieldValues = "fieldValues",
|
|
34
|
+
formData = "formData",
|
|
35
35
|
getAvailableForms = "getAvailableForms",
|
|
36
36
|
noFormsAvailable = "noFormsAvailable",
|
|
37
37
|
uagComponentNotFound = "uagComponentNotFound",
|
|
38
|
-
agentProcessData = "agentProcessData"
|
|
38
|
+
agentProcessData = "agentProcessData",
|
|
39
|
+
fetchExternalData = "fetchExternalData",
|
|
40
|
+
fetchExternalDataError = "fetchExternalDataError"
|
|
39
41
|
}
|
|
40
42
|
export declare class UAGTemplate {
|
|
41
43
|
config: UAGTemplateConfig;
|
package/lib/template.js
CHANGED
|
@@ -43,7 +43,6 @@ const lodash_1 = require("lodash");
|
|
|
43
43
|
var ResponseTemplate;
|
|
44
44
|
(function (ResponseTemplate) {
|
|
45
45
|
ResponseTemplate["collectedData"] = "collectedData";
|
|
46
|
-
ResponseTemplate["submittedData"] = "submittedData";
|
|
47
46
|
ResponseTemplate["fieldRules"] = "fieldRules";
|
|
48
47
|
ResponseTemplate["allFieldsCollected"] = "allFieldsCollected";
|
|
49
48
|
ResponseTemplate["getFormFieldsEmpty"] = "getFormFieldsEmpty";
|
|
@@ -70,10 +69,13 @@ var ResponseTemplate;
|
|
|
70
69
|
ResponseTemplate["fields"] = "fields";
|
|
71
70
|
ResponseTemplate["fieldList"] = "fieldList";
|
|
72
71
|
ResponseTemplate["fieldValues"] = "fieldValues";
|
|
72
|
+
ResponseTemplate["formData"] = "formData";
|
|
73
73
|
ResponseTemplate["getAvailableForms"] = "getAvailableForms";
|
|
74
74
|
ResponseTemplate["noFormsAvailable"] = "noFormsAvailable";
|
|
75
75
|
ResponseTemplate["uagComponentNotFound"] = "uagComponentNotFound";
|
|
76
76
|
ResponseTemplate["agentProcessData"] = "agentProcessData";
|
|
77
|
+
ResponseTemplate["fetchExternalData"] = "fetchExternalData";
|
|
78
|
+
ResponseTemplate["fetchExternalDataError"] = "fetchExternalDataError";
|
|
77
79
|
})(ResponseTemplate || (exports.ResponseTemplate = ResponseTemplate = {}));
|
|
78
80
|
class UAGTemplate {
|
|
79
81
|
constructor(config = {}) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
## Collected Data
|
|
2
|
-
|
|
2
|
+
Here is the data collected so far:
|
|
3
3
|
|
|
4
|
-
<% data.forEach(function(item) { %><%= item.prefix %>- **<%= item.label
|
|
4
|
+
<% data.forEach(function(item) { %><%= item.prefix %>- **<%= item.label %>** (`data_path`: "<%= item.path %>"): <%= item.value %>
|
|
5
5
|
<% }); %>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<% if (options) { %>Here are the available select options for **<%= label %>** (`data_path`: "<%= dataPath %>"):
|
|
2
|
+
|
|
3
|
+
<% options.forEach(function(option) { %>
|
|
4
|
+
- label: <%= option.label %>, value: `<%= typeof option.value === 'object' ? JSON.stringify(option.value) : option.value %>`<% }); %>
|
|
5
|
+
|
|
6
|
+
Use the **value** (not the label) when setting this field's data via `collect_field_data`.
|
|
7
|
+
<% } else if (formData) { %>The "fetched" value for **<%= label %>** (`data_path`: `<%= dataPath %>`) is included with following `form_data`:
|
|
8
|
+
|
|
9
|
+
<%= formData %>
|
|
10
|
+
<% } %>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Error fetching external data for field `<%= dataPath %>`: <%= error %>
|
|
2
|
+
|
|
3
|
+
Please verify:
|
|
4
|
+
- The form name and `data_path` are correct
|
|
5
|
+
- The field is a select component with `dataSrc` of "url" or "resource", or a datasource component
|
|
6
|
+
- The external data source is accessible
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
There are some validation errors in the data collected:
|
|
2
2
|
<% validationErrors.forEach(function(error) { %>
|
|
3
3
|
- <% if (error.label && error.path) { %>**<%= error.label %> (<%= error.path %>)**: <% } %><%= error.message %><% }); %>
|
|
4
4
|
|
|
@@ -83,9 +83,9 @@ export declare class SchemaBuilder {
|
|
|
83
83
|
*
|
|
84
84
|
* ```json
|
|
85
85
|
* [
|
|
86
|
-
* "email",
|
|
87
|
-
* "firstName",
|
|
88
|
-
* "lastName"
|
|
86
|
+
* "employee.email",
|
|
87
|
+
* "employee.firstName",
|
|
88
|
+
* "employee.lastName"
|
|
89
89
|
* ]
|
|
90
90
|
*/
|
|
91
91
|
fields_requested(): this;
|
|
@@ -113,6 +113,14 @@ export declare class SchemaBuilder {
|
|
|
113
113
|
* @returns
|
|
114
114
|
*/
|
|
115
115
|
persona(): this;
|
|
116
|
+
/**
|
|
117
|
+
* The data_path of a specific field within the form.
|
|
118
|
+
*/
|
|
119
|
+
data_path(): this;
|
|
120
|
+
/**
|
|
121
|
+
* An optional search value to filter select options server-side.
|
|
122
|
+
*/
|
|
123
|
+
search_value(): this;
|
|
116
124
|
/**
|
|
117
125
|
* An array of field updates to apply to the `form_data`.
|
|
118
126
|
* Each update specifies the `data_path` and the complete new value.
|
|
@@ -120,11 +128,11 @@ export declare class SchemaBuilder {
|
|
|
120
128
|
* ```json
|
|
121
129
|
* [
|
|
122
130
|
* {
|
|
123
|
-
* "data_path": "firstName",
|
|
131
|
+
* "data_path": "customer.firstName",
|
|
124
132
|
* "new_value": "John"
|
|
125
133
|
* },
|
|
126
134
|
* {
|
|
127
|
-
* "data_path": "lastName",
|
|
135
|
+
* "data_path": "customer.lastName",
|
|
128
136
|
* "new_value": "Doe"
|
|
129
137
|
* }
|
|
130
138
|
* ]
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.SchemaBuilder = void 0;
|
|
7
7
|
const zod_1 = __importDefault(require("zod"));
|
|
8
|
+
const DATA_PATH_DESCRIPTION = `The \`data_path\` (or "full path") to a component's instance value within a form submission (e.g., "customer.firstName"). Use \`get_form_fields\` to find the \`data_path\` of this component.`;
|
|
8
9
|
/**
|
|
9
10
|
* Schema builder for tool input schemas.
|
|
10
11
|
*/
|
|
@@ -42,7 +43,7 @@ class SchemaBuilder {
|
|
|
42
43
|
* ```
|
|
43
44
|
*/
|
|
44
45
|
form_data() {
|
|
45
|
-
this.schema.form_data = zod_1.default.record(zod_1.default.string().describe(
|
|
46
|
+
this.schema.form_data = zod_1.default.record(zod_1.default.string().describe(DATA_PATH_DESCRIPTION), zod_1.default.any().describe(`The value collected for the field. ${this.valueRules}`)).optional().default({}).describe('The current data collected so far for the whole form without any updates applied. Include ALL previously collected field data to maintain session state.');
|
|
46
47
|
return this;
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
@@ -63,7 +64,7 @@ class SchemaBuilder {
|
|
|
63
64
|
* ```
|
|
64
65
|
*/
|
|
65
66
|
field_paths() {
|
|
66
|
-
this.schema.field_paths = zod_1.default.array(zod_1.default.string()).describe(
|
|
67
|
+
this.schema.field_paths = zod_1.default.array(zod_1.default.string().describe(DATA_PATH_DESCRIPTION)).describe("An array of `data_path`(s) to get detailed information for specific fields.");
|
|
67
68
|
return this;
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
@@ -101,10 +102,23 @@ class SchemaBuilder {
|
|
|
101
102
|
*/
|
|
102
103
|
search_query() {
|
|
103
104
|
this.schema.search_query = zod_1.default.array(zod_1.default.object({
|
|
104
|
-
data_path: zod_1.default.string().describe(
|
|
105
|
-
operator: zod_1.default.enum([
|
|
105
|
+
data_path: zod_1.default.string().describe(DATA_PATH_DESCRIPTION),
|
|
106
|
+
operator: zod_1.default.enum([
|
|
107
|
+
'equals',
|
|
108
|
+
'not_equals',
|
|
109
|
+
'contains',
|
|
110
|
+
'starts_with',
|
|
111
|
+
'ends_with',
|
|
112
|
+
'regex',
|
|
113
|
+
'in',
|
|
114
|
+
'nin',
|
|
115
|
+
'greater_than',
|
|
116
|
+
'greater_than_equal',
|
|
117
|
+
'less_than',
|
|
118
|
+
'less_than_equal'
|
|
119
|
+
]).optional().default('contains').describe('The operator to use for matching. "equals" for exact match, "contains" for substring match, "starts_with" or "ends_with" for prefix/suffix match, "regex" for regular expression match, "greater_than" or "less_than" only for numeric values, "in" if searching for multiple values as comma-separated values, "nin" if excluding multiple values as comma-separated values.'),
|
|
106
120
|
search_value: zod_1.default.string().describe('The value of the field to use when searching.')
|
|
107
|
-
}).describe('Object containing the `data_path` and search value. Use `get_form_fields` to get all the field information to populate this search query object.')).describe('Array of search criteria to find matching submissions. All criteria must match (AND logic).
|
|
121
|
+
}).describe('Object containing the `data_path` and search value. Use `get_form_fields` to get all the field information to populate this search query object.')).describe('Array of search criteria to find matching submissions. All criteria must match (AND logic).');
|
|
108
122
|
return this;
|
|
109
123
|
}
|
|
110
124
|
/**
|
|
@@ -113,13 +127,13 @@ class SchemaBuilder {
|
|
|
113
127
|
*
|
|
114
128
|
* ```json
|
|
115
129
|
* [
|
|
116
|
-
* "email",
|
|
117
|
-
* "firstName",
|
|
118
|
-
* "lastName"
|
|
130
|
+
* "employee.email",
|
|
131
|
+
* "employee.firstName",
|
|
132
|
+
* "employee.lastName"
|
|
119
133
|
* ]
|
|
120
134
|
*/
|
|
121
135
|
fields_requested() {
|
|
122
|
-
this.schema.fields_requested = zod_1.default.array(zod_1.default.string()).optional().describe(
|
|
136
|
+
this.schema.fields_requested = zod_1.default.array(zod_1.default.string().describe(DATA_PATH_DESCRIPTION)).optional().describe("An array of `data_path`(s) for values you wish to include in the result. If not provided, only submission IDs and timestamps are returned. (e.g., [\"employee.email\"] - \"What is the email address for the employee Joe Smith?\")");
|
|
123
137
|
return this;
|
|
124
138
|
}
|
|
125
139
|
/**
|
|
@@ -161,6 +175,20 @@ class SchemaBuilder {
|
|
|
161
175
|
this.schema.persona = zod_1.default.string().optional().describe('The agent persona "type" to use when processing the existing submission data. The persona provides context to the agent on how to interpret and understand the data within the form submission. If not provided, the default persona defined within the form\'s UAG settings will be used.');
|
|
162
176
|
return this;
|
|
163
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* The data_path of a specific field within the form.
|
|
180
|
+
*/
|
|
181
|
+
data_path() {
|
|
182
|
+
this.schema.data_path = zod_1.default.string().describe(DATA_PATH_DESCRIPTION);
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* An optional search value to filter select options server-side.
|
|
187
|
+
*/
|
|
188
|
+
search_value() {
|
|
189
|
+
this.schema.search_value = zod_1.default.string().optional().describe('An optional search string to filter the select options server-side. The component must have a `searchField` configured for this to take effect.');
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
164
192
|
/**
|
|
165
193
|
* An array of field updates to apply to the `form_data`.
|
|
166
194
|
* Each update specifies the `data_path` and the complete new value.
|
|
@@ -168,11 +196,11 @@ class SchemaBuilder {
|
|
|
168
196
|
* ```json
|
|
169
197
|
* [
|
|
170
198
|
* {
|
|
171
|
-
* "data_path": "firstName",
|
|
199
|
+
* "data_path": "customer.firstName",
|
|
172
200
|
* "new_value": "John"
|
|
173
201
|
* },
|
|
174
202
|
* {
|
|
175
|
-
* "data_path": "lastName",
|
|
203
|
+
* "data_path": "customer.lastName",
|
|
176
204
|
* "new_value": "Doe"
|
|
177
205
|
* }
|
|
178
206
|
* ]
|
|
@@ -180,7 +208,7 @@ class SchemaBuilder {
|
|
|
180
208
|
*/
|
|
181
209
|
updates() {
|
|
182
210
|
this.schema.updates = zod_1.default.array(zod_1.default.object({
|
|
183
|
-
data_path: zod_1.default.string().describe(
|
|
211
|
+
data_path: zod_1.default.string().describe(DATA_PATH_DESCRIPTION),
|
|
184
212
|
new_value: zod_1.default.any().describe(`The complete new value for the field. ${this.valueRules}`)
|
|
185
213
|
}).describe('A single field update including the `data_path` and new value.')).describe('An array of field updates to apply to the `form_data`. Each update specifies the `data_path` and the complete new value.');
|
|
186
214
|
return this;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchExternalData = void 0;
|
|
4
|
+
const template_1 = require("../template");
|
|
5
|
+
const core_1 = require("@formio/core");
|
|
6
|
+
const SchemaBuilder_1 = require("./SchemaBuilder");
|
|
7
|
+
const lodash_1 = require("lodash");
|
|
8
|
+
const error = require('debug')('formio:uag:fetchExternalData:error');
|
|
9
|
+
/**
|
|
10
|
+
* Strip HTML tags from a string.
|
|
11
|
+
*/
|
|
12
|
+
function stripHtml(str) {
|
|
13
|
+
return str.replace(/<[^>]*>/g, '').trim();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extract label from an item using the component's template.
|
|
17
|
+
* For resource select items, the item is a full submission object with data nested under `item.data`.
|
|
18
|
+
* The template may reference `item.propertyName` which could be at `item.data.propertyName`.
|
|
19
|
+
*/
|
|
20
|
+
function extractLabel(item, component, evalContext) {
|
|
21
|
+
if (component.template) {
|
|
22
|
+
evalContext.item = item; // Add the current item to the interpolation context for template evaluation
|
|
23
|
+
const label = core_1.Evaluator.interpolateString(component.template, evalContext);
|
|
24
|
+
if (label !== undefined && label !== null) {
|
|
25
|
+
return stripHtml(String(label));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Fallback: try common label properties
|
|
29
|
+
return item.label || item.name || item.title || String(item);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extract value from an item using the component's valueProperty.
|
|
33
|
+
*/
|
|
34
|
+
function extractValue(item, component) {
|
|
35
|
+
if (component.valueProperty) {
|
|
36
|
+
return (0, lodash_1.get)(item, component.valueProperty);
|
|
37
|
+
}
|
|
38
|
+
return item;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if a component is a select-type component with external data (url or resource).
|
|
42
|
+
*/
|
|
43
|
+
function isExternalSelect(component) {
|
|
44
|
+
if (component.type !== 'select' && component.type !== 'selectboxes')
|
|
45
|
+
return false;
|
|
46
|
+
const dataSrc = component.dataSrc;
|
|
47
|
+
return dataSrc === 'url' || dataSrc === 'resource';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if a component is a datasource component.
|
|
51
|
+
*/
|
|
52
|
+
function isDatasource(component) {
|
|
53
|
+
return component.type === 'datasource';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Interpolate header key/value pairs, filtering out empty ones.
|
|
57
|
+
*/
|
|
58
|
+
function interpolateHeaders(headers, context) {
|
|
59
|
+
const result = {};
|
|
60
|
+
if (!headers || !Array.isArray(headers))
|
|
61
|
+
return result;
|
|
62
|
+
for (const header of headers) {
|
|
63
|
+
const key = header.key ? core_1.Evaluator.interpolateString(header.key, context) : '';
|
|
64
|
+
const value = header.value ? core_1.Evaluator.interpolateString(header.value, context) : '';
|
|
65
|
+
if (key && value) {
|
|
66
|
+
result[key] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
const fetchExternalData = async (project) => {
|
|
72
|
+
return (0, lodash_1.defaultsDeep)(project.config?.toolOverrides?.fetch_external_data || {}, {
|
|
73
|
+
name: 'fetch_external_data',
|
|
74
|
+
title: 'Fetch External Data',
|
|
75
|
+
description: 'Fetch external data for a component that loads data from an external URL, a Form.io Resource, or a DataSource. Use this tool when `get_form_fields` indicates that a field requires calling `fetch_external_data` to retrieve its data. For select components, this returns the available options. For datasource components, this returns the fetched data. You can optionally provide a `search_value` to filter results server-side. If the component\'s URL or headers contain interpolation tokens (e.g. `{{ data.someField }}`), you MUST provide `form_data` with the current collected data so the tokens can be resolved.',
|
|
76
|
+
inputSchema: (new SchemaBuilder_1.SchemaBuilder(project))
|
|
77
|
+
.form_name()
|
|
78
|
+
.data_path()
|
|
79
|
+
.form_data()
|
|
80
|
+
.search_value().schema,
|
|
81
|
+
execute: async ({ form_name, data_path, form_data, search_value }, extra) => {
|
|
82
|
+
const form = await project.getForm(form_name);
|
|
83
|
+
if (!form) {
|
|
84
|
+
return project.mcpResponse(template_1.ResponseTemplate.formNotFound, { formName: form_name }, true);
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const component = form.getComponent(data_path);
|
|
88
|
+
if (!component) {
|
|
89
|
+
return project.mcpResponse(template_1.ResponseTemplate.fetchExternalDataError, {
|
|
90
|
+
dataPath: data_path,
|
|
91
|
+
error: `No component found at path "${data_path}".`
|
|
92
|
+
}, true);
|
|
93
|
+
}
|
|
94
|
+
// Build the interpolation context from the current form data.
|
|
95
|
+
const submission = form_data ? form.convertToSubmission(form_data) : { data: {} };
|
|
96
|
+
const evalContext = {
|
|
97
|
+
form: form.form,
|
|
98
|
+
component,
|
|
99
|
+
components: form.form.components,
|
|
100
|
+
data: submission.data || {},
|
|
101
|
+
row: submission.data || {},
|
|
102
|
+
submission
|
|
103
|
+
};
|
|
104
|
+
if (isDatasource(component)) {
|
|
105
|
+
const items = await fetchDatasourceData(component, extra.authInfo, evalContext);
|
|
106
|
+
(0, lodash_1.set)(submission.data || {}, data_path, items);
|
|
107
|
+
const errors = await form.validateData(submission, extra.authInfo);
|
|
108
|
+
if (errors?.length > 0) {
|
|
109
|
+
return project.mcpResponse(template_1.ResponseTemplate.fieldValidationErrors, { invalidFields: errors });
|
|
110
|
+
}
|
|
111
|
+
return project.mcpResponse(template_1.ResponseTemplate.fetchExternalData, {
|
|
112
|
+
label: component.label || component.key,
|
|
113
|
+
dataPath: data_path,
|
|
114
|
+
options: false,
|
|
115
|
+
formData: project.uagTemplate?.renderTemplate(template_1.ResponseTemplate.formData, {
|
|
116
|
+
data: form.formatData(submission.data)
|
|
117
|
+
})
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (!isExternalSelect(component)) {
|
|
121
|
+
return project.mcpResponse(template_1.ResponseTemplate.fetchExternalDataError, {
|
|
122
|
+
dataPath: data_path,
|
|
123
|
+
error: `Component at "${data_path}" is of type "${component.type}" and does not load external data. This tool only supports select/selectboxes components with dataSrc "url" or "resource", and datasource components.`
|
|
124
|
+
}, true);
|
|
125
|
+
}
|
|
126
|
+
const selectComponent = component;
|
|
127
|
+
const dataSrc = selectComponent.dataSrc;
|
|
128
|
+
if (dataSrc !== 'url' && dataSrc !== 'resource') {
|
|
129
|
+
return project.mcpResponse(template_1.ResponseTemplate.fetchExternalDataError, {
|
|
130
|
+
dataPath: data_path,
|
|
131
|
+
error: `Component at "${data_path}" has dataSrc "${dataSrc}". Only "url" and "resource" are supported by this tool.`
|
|
132
|
+
}, true);
|
|
133
|
+
}
|
|
134
|
+
let items = [];
|
|
135
|
+
if (dataSrc === 'url') {
|
|
136
|
+
items = await fetchUrlData(selectComponent, search_value, extra.authInfo, evalContext);
|
|
137
|
+
}
|
|
138
|
+
else if (dataSrc === 'resource') {
|
|
139
|
+
items = await fetchResourceData(selectComponent, search_value, extra.authInfo, project, evalContext);
|
|
140
|
+
}
|
|
141
|
+
const options = items.map(item => ({
|
|
142
|
+
label: extractLabel(item, selectComponent, evalContext),
|
|
143
|
+
value: extractValue(item, selectComponent)
|
|
144
|
+
}));
|
|
145
|
+
return project.mcpResponse(template_1.ResponseTemplate.fetchExternalData, {
|
|
146
|
+
label: selectComponent.label || selectComponent.key,
|
|
147
|
+
dataPath: data_path,
|
|
148
|
+
options,
|
|
149
|
+
formData: {}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
error('Error fetching external data:', err);
|
|
154
|
+
return project.mcpResponse(template_1.ResponseTemplate.fetchExternalDataError, {
|
|
155
|
+
dataPath: data_path,
|
|
156
|
+
error: err instanceof Error ? err.message : 'Unknown error'
|
|
157
|
+
}, true);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
exports.fetchExternalData = fetchExternalData;
|
|
163
|
+
async function fetchDatasourceData(component, authInfo, context) {
|
|
164
|
+
const fetchConfig = component.fetch;
|
|
165
|
+
if (!fetchConfig?.url) {
|
|
166
|
+
throw new Error('No URL configured for this datasource component.');
|
|
167
|
+
}
|
|
168
|
+
// Interpolate the URL
|
|
169
|
+
const interpolatedUrl = core_1.Evaluator.interpolateString(fetchConfig.url, context);
|
|
170
|
+
const url = new URL(interpolatedUrl);
|
|
171
|
+
// Build and interpolate headers
|
|
172
|
+
const headers = interpolateHeaders(fetchConfig.headers, context);
|
|
173
|
+
// Add auth token if forwardHeaders or authenticate is enabled
|
|
174
|
+
if ((fetchConfig.forwardHeaders || fetchConfig.authenticate) && authInfo?.token) {
|
|
175
|
+
headers['x-jwt-token'] = authInfo.token;
|
|
176
|
+
}
|
|
177
|
+
const response = await fetch(url.toString(), {
|
|
178
|
+
method: (fetchConfig.method || 'get').toUpperCase(),
|
|
179
|
+
headers
|
|
180
|
+
});
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
183
|
+
}
|
|
184
|
+
let result = await response.json();
|
|
185
|
+
if (!Array.isArray(result)) {
|
|
186
|
+
// If the result has a 'results' property that is an array, use that
|
|
187
|
+
if (result.results && Array.isArray(result.results)) {
|
|
188
|
+
result = result.results;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// Wrap single objects in an array
|
|
192
|
+
result = [result];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
async function fetchUrlData(component, search_value, authInfo, context) {
|
|
198
|
+
const data = component.data;
|
|
199
|
+
const baseUrl = data?.url;
|
|
200
|
+
if (!baseUrl) {
|
|
201
|
+
throw new Error('No URL configured for this component.');
|
|
202
|
+
}
|
|
203
|
+
// Interpolate the URL
|
|
204
|
+
const interpolatedUrl = core_1.Evaluator.interpolateString(baseUrl, context);
|
|
205
|
+
const url = new URL(interpolatedUrl);
|
|
206
|
+
// Add filter query string if configured (interpolate the filter string)
|
|
207
|
+
if (component.filter) {
|
|
208
|
+
const interpolatedFilter = core_1.Evaluator.interpolateString(component.filter, context);
|
|
209
|
+
const filterParams = new URLSearchParams(interpolatedFilter);
|
|
210
|
+
filterParams.forEach((value, key) => {
|
|
211
|
+
url.searchParams.set(key, value);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// Add search parameter if provided
|
|
215
|
+
if (search_value && component.searchField) {
|
|
216
|
+
url.searchParams.set(component.searchField, search_value);
|
|
217
|
+
}
|
|
218
|
+
// Add limit
|
|
219
|
+
const limit = component.limit || 100;
|
|
220
|
+
url.searchParams.set('limit', String(limit));
|
|
221
|
+
// Build and interpolate headers
|
|
222
|
+
const headers = interpolateHeaders(data.headers, context);
|
|
223
|
+
// Add auth token if authenticate is enabled
|
|
224
|
+
if (component.authenticate && authInfo?.token) {
|
|
225
|
+
headers['x-jwt-token'] = authInfo.token;
|
|
226
|
+
}
|
|
227
|
+
const response = await fetch(url.toString(), { headers });
|
|
228
|
+
if (!response.ok) {
|
|
229
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
230
|
+
}
|
|
231
|
+
let result = await response.json();
|
|
232
|
+
// Navigate to selectValues path if configured
|
|
233
|
+
if (component.selectValues) {
|
|
234
|
+
result = (0, lodash_1.get)(result, component.selectValues, result);
|
|
235
|
+
}
|
|
236
|
+
if (!Array.isArray(result)) {
|
|
237
|
+
throw new Error('Response is not an array. Check the URL and selectValues configuration.');
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
async function fetchResourceData(component, search_value, authInfo, project, context) {
|
|
242
|
+
const data = component.data;
|
|
243
|
+
const resourceId = data?.resource;
|
|
244
|
+
if (!resourceId) {
|
|
245
|
+
throw new Error('No resource ID configured for this component.');
|
|
246
|
+
}
|
|
247
|
+
const resourceForm = await project.getFormById(resourceId);
|
|
248
|
+
if (!resourceForm) {
|
|
249
|
+
throw new Error(`Resource form with ID "${resourceId}" not found.`);
|
|
250
|
+
}
|
|
251
|
+
const query = {};
|
|
252
|
+
// Add search filter if provided
|
|
253
|
+
if (search_value && component.searchField) {
|
|
254
|
+
query[`data.${component.searchField}__regex`] = `/${search_value}/i`;
|
|
255
|
+
}
|
|
256
|
+
// Add component filter if configured (interpolate the filter string)
|
|
257
|
+
if (component.filter) {
|
|
258
|
+
const interpolatedFilter = core_1.Evaluator.interpolateString(component.filter, context);
|
|
259
|
+
const filterParams = new URLSearchParams(interpolatedFilter);
|
|
260
|
+
filterParams.forEach((value, key) => {
|
|
261
|
+
query[key] = value;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// Add limit
|
|
265
|
+
query.limit = component.limit || 100;
|
|
266
|
+
const result = await resourceForm.find(query, authInfo);
|
|
267
|
+
return result.items || [];
|
|
268
|
+
}
|
package/lib/tools/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { findSubmission } from "./findSubmission";
|
|
|
8
8
|
import { submissionUpdate } from "./submissionUpdate";
|
|
9
9
|
import { ToolInfo } from "./utils";
|
|
10
10
|
import { UAGProjectInterface } from "../UAGProjectInterface";
|
|
11
|
+
import { fetchExternalData } from "./fetchExternalData";
|
|
11
12
|
export * from './utils';
|
|
12
|
-
export { getForms, getFormFields, getFieldInfo, collectData, confirmSubmission, submitCompletedForm, findSubmission, submissionUpdate };
|
|
13
|
+
export { getForms, getFormFields, getFieldInfo, collectData, confirmSubmission, submitCompletedForm, findSubmission, submissionUpdate, fetchExternalData };
|
|
13
14
|
export declare const getTools: (project: UAGProjectInterface) => Promise<ToolInfo[]>;
|
package/lib/tools/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.getTools = exports.submissionUpdate = exports.findSubmission = exports.submitCompletedForm = exports.confirmSubmission = exports.collectData = exports.getFieldInfo = exports.getFormFields = exports.getForms = void 0;
|
|
17
|
+
exports.getTools = exports.fetchExternalData = exports.submissionUpdate = exports.findSubmission = exports.submitCompletedForm = exports.confirmSubmission = exports.collectData = exports.getFieldInfo = exports.getFormFields = exports.getForms = void 0;
|
|
18
18
|
const getForms_1 = require("./getForms");
|
|
19
19
|
Object.defineProperty(exports, "getForms", { enumerable: true, get: function () { return getForms_1.getForms; } });
|
|
20
20
|
const getFormFields_1 = require("./getFormFields");
|
|
@@ -32,6 +32,8 @@ Object.defineProperty(exports, "findSubmission", { enumerable: true, get: functi
|
|
|
32
32
|
const submissionUpdate_1 = require("./submissionUpdate");
|
|
33
33
|
Object.defineProperty(exports, "submissionUpdate", { enumerable: true, get: function () { return submissionUpdate_1.submissionUpdate; } });
|
|
34
34
|
const agentProvideData_1 = require("./agentProvideData");
|
|
35
|
+
const fetchExternalData_1 = require("./fetchExternalData");
|
|
36
|
+
Object.defineProperty(exports, "fetchExternalData", { enumerable: true, get: function () { return fetchExternalData_1.fetchExternalData; } });
|
|
35
37
|
__exportStar(require("./utils"), exports);
|
|
36
38
|
const getTools = async (project) => {
|
|
37
39
|
return [
|
|
@@ -43,7 +45,8 @@ const getTools = async (project) => {
|
|
|
43
45
|
await (0, submitForm_1.submitCompletedForm)(project),
|
|
44
46
|
await (0, findSubmission_1.findSubmission)(project),
|
|
45
47
|
await (0, submissionUpdate_1.submissionUpdate)(project),
|
|
46
|
-
await (0, agentProvideData_1.agentProvideData)(project)
|
|
48
|
+
await (0, agentProvideData_1.agentProvideData)(project),
|
|
49
|
+
await (0, fetchExternalData_1.fetchExternalData)(project)
|
|
47
50
|
];
|
|
48
51
|
};
|
|
49
52
|
exports.getTools = getTools;
|
package/lib/tools/submitForm.js
CHANGED
|
@@ -40,20 +40,12 @@ const submitCompletedForm = async (project) => {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
catch (err) {
|
|
43
|
-
|
|
43
|
+
let errors = [];
|
|
44
44
|
if (err && err.name === 'ValidationError' && err.details) {
|
|
45
|
-
|
|
46
|
-
if (detail.message) {
|
|
47
|
-
errors.push({
|
|
48
|
-
label: detail.context?.label || 'Field',
|
|
49
|
-
path: detail.context?.path,
|
|
50
|
-
message: detail.message
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
45
|
+
errors = form.convertToFormFieldErrors(err.details);
|
|
54
46
|
}
|
|
55
47
|
return project.mcpResponse(template_1.ResponseTemplate.submitValidationError, {
|
|
56
|
-
validationErrors: errors.length ? errors : [{ message: err.message ||
|
|
48
|
+
validationErrors: errors.length ? errors : [{ message: err.message || err.toString() }]
|
|
57
49
|
}, true);
|
|
58
50
|
}
|
|
59
51
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formio/uag",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "The Form.io Universal Agent Gateway (UAG).",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
"author": "",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@formio/appserver": "^2.
|
|
27
|
+
"@formio/appserver": "^2.12.0",
|
|
28
28
|
"@formio/core": "2.5.1-dev.291.6557e4e",
|
|
29
|
-
"@modelcontextprotocol/sdk": "
|
|
29
|
+
"@modelcontextprotocol/sdk": "1.27.0",
|
|
30
30
|
"cors": "^2.8.6",
|
|
31
|
-
"debug": "^4.4.
|
|
32
|
-
"dotenv": "^17.
|
|
31
|
+
"debug": "^4.4.3",
|
|
32
|
+
"dotenv": "^17.3.1",
|
|
33
33
|
"express": "^5.2.1",
|
|
34
|
-
"jsonwebtoken": "^9.0.
|
|
34
|
+
"jsonwebtoken": "^9.0.3",
|
|
35
35
|
"lodash": "^4.17.23",
|
|
36
36
|
"node-cache": "^5.1.2",
|
|
37
37
|
"node-html-markdown": "^2.0.0",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"@types/jsonwebtoken": "^9.0.10",
|
|
47
47
|
"@types/lodash": "^4.17.23",
|
|
48
48
|
"@types/mocha": "^10.0.10",
|
|
49
|
-
"@types/node": "^25.
|
|
49
|
+
"@types/node": "^25.3.0",
|
|
50
50
|
"@types/supertest": "^6.0.3",
|
|
51
51
|
"chai": "^6.2.2",
|
|
52
|
-
"mocha": "^11.
|
|
52
|
+
"mocha": "^11.7.5",
|
|
53
53
|
"supertest": "^7.2.2",
|
|
54
54
|
"terser": "^5.46.0",
|
|
55
55
|
"ts-node": "^10.9.2",
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
**Submission Data Summary:** (IMPORTANT NOTE: When presenting the values to the user, it must appear exactly as they are in the submission; e.g "user@example.com", "(555) 123-4560")
|
|
2
|
-
<% data.forEach(function(item) { %>
|
|
3
|
-
- **<%= item.label %>**: <%= item.value %>
|
|
4
|
-
<% }); %>
|